From da06b000c1e3707299c80516ac46b096b24ea608 Mon Sep 17 00:00:00 2001 From: "akpaev.e" Date: Wed, 26 Feb 2025 11:32:51 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=BC=D0=B5=D0=B6=D1=83?= =?UTF-8?q?=D1=82=D0=BE=D1=87=D0=BD=D0=B0=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ => RemoteAdministration}/Rac.cs | 65 ++-- .../{ => RemoteAdministration}/V8Cluster.cs | 2 +- .../{ => RemoteAdministration}/V8InfoBase.cs | 2 +- .../V8InfoBaseSummary.cs | 2 +- .../{ => RemoteAdministration}/V8Session.cs | 2 +- .../Platform/{ => Services}/ArgsParser.cs | 2 +- .../Platform/{ => Services}/RagentService.cs | 3 +- .../Platform/{ => Services}/RasService.cs | 3 +- .../Platform/{ => Services}/V8Service.cs | 2 +- .../Platform/{ => Services}/V8ServiceType.cs | 2 +- .../Platform/{ => Services}/V8Services.cs | 2 +- .../Platform/Unpack/BlockHeader.cs | 55 ++++ .../Platform/Unpack/BlockReader.cs | 152 +++++++++ .../Platform/Unpack/ContainerHeader.cs | 34 ++ .../Platform/Unpack/ElementAddress.cs | 28 ++ .../Platform/Unpack/ElementHeader.cs | 29 ++ OneSTools.Common/Platform/Unpack/File8.cs | 18 + .../Platform/Unpack/File8Collection.cs | 45 +++ .../Platform/Unpack/File8FormatException.cs | 3 + .../Platform/Unpack/File8Reader.cs | 138 ++++++++ .../Platform/Unpack/FileFormat.cs | 23 ++ .../Asserts/ПодтверждениеЛегальности.epf | Bin 0 -> 6661 bytes onecmonitor-agent/Program.cs | 9 +- onecmonitor-agent/Services/CommandsWatcher.cs | 28 +- .../InfoBases/InfoBasesUpdateTasksQueue.cs | 15 + .../Services/InfoBases/InfoBasesUpdater.cs | 310 ++++++++++-------- .../Services/InfoBases/RasHolder.cs | 3 +- .../Services/OnecMonitorConnection.cs | 3 +- onecmonitor-agent/appsettings.json | 2 +- onecmonitor-agent/onecmonitor-agent.csproj | 1 + .../DTO/UpdateInfoBaseTaskDto.cs | 4 +- .../DTO/{ConfigurationDto.cs => V8FileDto.cs} | 2 +- onecmonitor-common/FastConnection.cs | 58 ++-- onecmonitor-common/Protos/onecmonitor.proto | 34 ++ onecmonitor-common/ServerConnection.cs | 2 +- onecmonitor-server/AppDbContext.cs | 5 +- .../AutoMapper/CommonProfile.cs | 36 +- onecmonitor-server/AutoMapper/DtoProfile.cs | 2 +- .../Controllers/CredentialsController.cs | 2 +- .../Controllers/MaintenanceController.cs | 73 ----- .../Controllers/MaintenanceTasksController.cs | 182 ++++++++++ .../UpdateInfoBaseTasksController.cs | 18 +- ...ionsController.cs => V8FilesController.cs} | 67 ++-- .../Extensions/EnumExtension.cs | 17 + onecmonitor-server/Helpers/UiHelper.cs | 5 +- ....cs => 20250225183414_Initial.Designer.cs} | 161 ++++++++- ...7_Initial.cs => 20250225183414_Initial.cs} | 182 ++++++++-- .../Migrations/AppDbContextModelSnapshot.cs | 159 ++++++++- onecmonitor-server/Models/MaintenanceStep.cs | 11 - .../MaintenanceTasks/MaintenanceStep.cs | 16 + .../MaintenanceStepKind.cs | 2 +- .../MaintenanceTasks/MaintenanceStepNode.cs | 19 ++ .../MaintenanceStepNodeKind.cs | 11 + .../MaintenanceTasks/MaintenanceTask.cs | 16 + .../Models/UpdateInfoBaseTask.cs | 2 +- .../Models/{V8Configuration.cs => V8File.cs} | 5 +- onecmonitor-server/Program.cs | 3 + .../Scripts/MaintenanceTask/editStepDialog.ts | 122 +++++++ .../maintenanceStepNodeKind.ts | 4 + .../Scripts/MaintenanceTask/nodesGraph.ts | 32 ++ .../nodesGraphActionsDialog.ts | 104 ++++++ .../MaintenanceTask/nodesGraphOptions.ts | 6 + .../MaintenanceTask/stepValidationResult.ts | 4 + .../MaintenanceTask/stepsEditorHelper.ts | 165 ++++++++++ onecmonitor-server/Scripts/index.ts | 27 +- .../Scripts/infoBasesUpdateTask.ts | 32 ++ .../Services/AgentConnection.cs | 5 +- .../ViewModels/Agents/AgentEditViewModel.cs | 1 + .../ConfigurationsIndexViewModel.cs | 6 - .../Maintenance/MaintenanceEditViewModel.cs | 32 -- .../Maintenance/MaintenanceIndexViewModel.cs | 6 - .../MaintenanceListItemViewModel.cs | 7 - .../MaintenanceStepNodeViewModel.cs | 22 ++ .../MaintenanceStepViewModel.cs | 24 +- .../MaintenanceTaskEditViewModel.cs | 21 ++ .../MaintenanceTaskListItemViewModel.cs | 7 + .../MaintenanceTasksIndexViewModel.cs | 6 + .../UpdateInfoBaseTaskEditViewModel.cs | 4 +- .../V8FileEditViewModel.cs} | 4 +- .../V8FileListItemViewModel.cs} | 8 +- .../V8Files/V8FilesIndexViewModel.cs | 6 + .../Views/MaintenanceTasks/Edit.cshtml | 101 ++++++ .../Views/MaintenanceTasks/EditStep.cshtml | 48 +++ .../Views/MaintenanceTasks/Index.cshtml | 43 +++ .../Views/Shared/_Layout.cshtml | 9 +- .../Views/UpdateInfoBaseTasks/Edit.cshtml | 11 +- .../{Configurations => V8Files}/Edit.cshtml | 74 ++--- .../{Configurations => V8Files}/Index.cshtml | 90 ++--- onecmonitor-server/appsettings.json | 2 +- onecmonitor-server/onecmonitor-server.csproj | 9 + onecmonitor-server/package.json | 4 +- onecmonitor-server/tsconfig.json | 5 + onecmonitor-server/webpack.config.js | 41 --- 93 files changed, 2554 insertions(+), 615 deletions(-) rename OneSTools.Common/Platform/{ => RemoteAdministration}/Rac.cs (74%) rename OneSTools.Common/Platform/{ => RemoteAdministration}/V8Cluster.cs (81%) rename OneSTools.Common/Platform/{ => RemoteAdministration}/V8InfoBase.cs (86%) rename OneSTools.Common/Platform/{ => RemoteAdministration}/V8InfoBaseSummary.cs (74%) rename OneSTools.Common/Platform/{ => RemoteAdministration}/V8Session.cs (86%) rename OneSTools.Common/Platform/{ => Services}/ArgsParser.cs (98%) rename OneSTools.Common/Platform/{ => Services}/RagentService.cs (83%) rename OneSTools.Common/Platform/{ => Services}/RasService.cs (88%) rename OneSTools.Common/Platform/{ => Services}/V8Service.cs (86%) rename OneSTools.Common/Platform/{ => Services}/V8ServiceType.cs (57%) rename OneSTools.Common/Platform/{ => Services}/V8Services.cs (99%) create mode 100644 OneSTools.Common/Platform/Unpack/BlockHeader.cs create mode 100644 OneSTools.Common/Platform/Unpack/BlockReader.cs create mode 100644 OneSTools.Common/Platform/Unpack/ContainerHeader.cs create mode 100644 OneSTools.Common/Platform/Unpack/ElementAddress.cs create mode 100644 OneSTools.Common/Platform/Unpack/ElementHeader.cs create mode 100644 OneSTools.Common/Platform/Unpack/File8.cs create mode 100644 OneSTools.Common/Platform/Unpack/File8Collection.cs create mode 100644 OneSTools.Common/Platform/Unpack/File8FormatException.cs create mode 100644 OneSTools.Common/Platform/Unpack/File8Reader.cs create mode 100644 OneSTools.Common/Platform/Unpack/FileFormat.cs create mode 100644 onecmonitor-agent/Asserts/ПодтверждениеЛегальности.epf create mode 100644 onecmonitor-agent/Services/InfoBases/InfoBasesUpdateTasksQueue.cs rename onecmonitor-common/DTO/{ConfigurationDto.cs => V8FileDto.cs} (94%) create mode 100644 onecmonitor-common/Protos/onecmonitor.proto delete mode 100644 onecmonitor-server/Controllers/MaintenanceController.cs create mode 100644 onecmonitor-server/Controllers/MaintenanceTasksController.cs rename onecmonitor-server/Controllers/{ConfigurationsController.cs => V8FilesController.cs} (65%) create mode 100644 onecmonitor-server/Extensions/EnumExtension.cs rename onecmonitor-server/Migrations/{20250214151907_Initial.Designer.cs => 20250225183414_Initial.Designer.cs} (75%) rename onecmonitor-server/Migrations/{20250214151907_Initial.cs => 20250225183414_Initial.cs} (73%) delete mode 100644 onecmonitor-server/Models/MaintenanceStep.cs create mode 100644 onecmonitor-server/Models/MaintenanceTasks/MaintenanceStep.cs rename onecmonitor-server/Models/{ => MaintenanceTasks}/MaintenanceStepKind.cs (93%) create mode 100644 onecmonitor-server/Models/MaintenanceTasks/MaintenanceStepNode.cs create mode 100644 onecmonitor-server/Models/MaintenanceTasks/MaintenanceStepNodeKind.cs create mode 100644 onecmonitor-server/Models/MaintenanceTasks/MaintenanceTask.cs rename onecmonitor-server/Models/{V8Configuration.cs => V8File.cs} (80%) create mode 100644 onecmonitor-server/Scripts/MaintenanceTask/editStepDialog.ts create mode 100644 onecmonitor-server/Scripts/MaintenanceTask/maintenanceStepNodeKind.ts create mode 100644 onecmonitor-server/Scripts/MaintenanceTask/nodesGraph.ts create mode 100644 onecmonitor-server/Scripts/MaintenanceTask/nodesGraphActionsDialog.ts create mode 100644 onecmonitor-server/Scripts/MaintenanceTask/nodesGraphOptions.ts create mode 100644 onecmonitor-server/Scripts/MaintenanceTask/stepValidationResult.ts create mode 100644 onecmonitor-server/Scripts/MaintenanceTask/stepsEditorHelper.ts create mode 100644 onecmonitor-server/Scripts/infoBasesUpdateTask.ts delete mode 100644 onecmonitor-server/ViewModels/Configurations/ConfigurationsIndexViewModel.cs delete mode 100644 onecmonitor-server/ViewModels/Maintenance/MaintenanceEditViewModel.cs delete mode 100644 onecmonitor-server/ViewModels/Maintenance/MaintenanceIndexViewModel.cs delete mode 100644 onecmonitor-server/ViewModels/Maintenance/MaintenanceListItemViewModel.cs create mode 100644 onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceStepNodeViewModel.cs rename onecmonitor-server/ViewModels/{Maintenance => MaintenanceTasks}/MaintenanceStepViewModel.cs (50%) create mode 100644 onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceTaskEditViewModel.cs create mode 100644 onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceTaskListItemViewModel.cs create mode 100644 onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceTasksIndexViewModel.cs rename onecmonitor-server/ViewModels/{Configurations/ConfigurationEditViewModel.cs => V8Files/V8FileEditViewModel.cs} (70%) rename onecmonitor-server/ViewModels/{Configurations/ConfigurationListItemViewModel.cs => V8Files/V8FileListItemViewModel.cs} (75%) create mode 100644 onecmonitor-server/ViewModels/V8Files/V8FilesIndexViewModel.cs create mode 100644 onecmonitor-server/Views/MaintenanceTasks/Edit.cshtml create mode 100644 onecmonitor-server/Views/MaintenanceTasks/EditStep.cshtml create mode 100644 onecmonitor-server/Views/MaintenanceTasks/Index.cshtml rename onecmonitor-server/Views/{Configurations => V8Files}/Edit.cshtml (77%) rename onecmonitor-server/Views/{Configurations => V8Files}/Index.cshtml (81%) delete mode 100644 onecmonitor-server/webpack.config.js diff --git a/OneSTools.Common/Platform/Rac.cs b/OneSTools.Common/Platform/RemoteAdministration/Rac.cs similarity index 74% rename from OneSTools.Common/Platform/Rac.cs rename to OneSTools.Common/Platform/RemoteAdministration/Rac.cs index f659e06..1539e11 100644 --- a/OneSTools.Common/Platform/Rac.cs +++ b/OneSTools.Common/Platform/RemoteAdministration/Rac.cs @@ -1,7 +1,8 @@ using System.Diagnostics; using OneSTools.Common.Extensions; +using OneSTools.Common.Platform.Services; -namespace OneSTools.Common.Platform; +namespace OneSTools.Common.Platform.RemoteAdministration; public class Rac(V8Platform platform, string host = "localhost", int port = 1545) { @@ -14,7 +15,7 @@ public class Rac(V8Platform platform, string host = "localhost", int port = 1545 } public List GetClusters() - => GetOutputItems("cluster list") + => GetOutputItems("cluster list", 10) .Select(c => new V8Cluster { Id = c["cluster"], @@ -23,12 +24,9 @@ public class Rac(V8Platform platform, string host = "localhost", int port = 1545 Port = int.Parse(c["port"]) }) .ToList(); - - public List GetInfoBasesSummaries(V8Cluster cluster) - => GetInfoBasesSummaries(cluster.Id); public List GetInfoBasesSummaries(string clusterId) - => GetOutputItems($"infobase --cluster={clusterId} summary list") + => GetOutputItems($"infobase --cluster={clusterId} summary list", 10) .Select(c => new V8InfoBaseSummary() { Id = c["infobase"], @@ -36,11 +34,8 @@ public class Rac(V8Platform platform, string host = "localhost", int port = 1545 }) .ToList(); - public V8InfoBase GetInfoBase(V8Cluster cluster, V8InfoBaseSummary infoBaseSummary, string user, string password) - => GetInfoBase(cluster.Id, infoBaseSummary.Name, user, password); - public V8InfoBase GetInfoBase(string clusterId, string infoBaseId, string user, string password) - => GetOutputItems($"infobase --cluster={clusterId} info --infobase={infoBaseId} --infobase-user={user} --infobase-pwd={password}") + => GetOutputItems($"infobase --cluster={clusterId} info --infobase={infoBaseId} --infobase-user={user} --infobase-pwd={password}", 20) .Select(c => new V8InfoBase { Id = c["infobase"], @@ -54,16 +49,13 @@ public class Rac(V8Platform platform, string host = "localhost", int port = 1545 public void BlockConnections(string clusterId, string infoBaseId, string user, string password, string permissionCode, string deniedMessage) - => StartRacAndGetOutput($"infobase --cluster={clusterId} update --infobase={infoBaseId} --infobase-user={user} --infobase-pwd={password} --sessions-deny=on --scheduled-jobs-deny=on --permission-code={permissionCode} --denied-message=\"{deniedMessage}\""); - - public List GetInfoBaseSessions(V8Cluster cluster, V8InfoBase infoBase) - => GetInfoBaseSessions(cluster.Id, infoBase.Id); + => StartRacAndGetOutput($"infobase --cluster={clusterId} update --infobase={infoBaseId} --infobase-user={user} --infobase-pwd={password} --sessions-deny=on --scheduled-jobs-deny=on --permission-code={permissionCode} --denied-message=\"{deniedMessage}\"", 10); public List GetInfoBaseSessions(string clusterId, string infoBaseId) { var infoBases = GetInfoBasesSummaries(clusterId); - return GetOutputItems($"session --cluster={clusterId} list --infobase={infoBaseId}") + return GetOutputItems($"session --cluster={clusterId} list --infobase={infoBaseId}", 20) .Select(c => new V8Session { Id = c["session"], @@ -76,21 +68,15 @@ public class Rac(V8Platform platform, string host = "localhost", int port = 1545 .ToList(); } - public void TerminateSession(V8Cluster cluster, V8Session session) - => TerminateSession(cluster.Id, session.Id); - public void TerminateSession(string clusterId, string sessionId) - => StartRacAndGetOutput($"session --cluster={clusterId} terminate --session={sessionId}"); - - public void UnblockConnections(V8Cluster cluster, V8InfoBase infoBase, string user, string password) - => UnblockConnections(cluster.Id, infoBase.Id, user, password); + => StartRacAndGetOutput($"session --cluster={clusterId} terminate --session={sessionId}", 10); public void UnblockConnections(string clusterId, string infoBaseId, string user, string password) - => StartRacAndGetOutput($"infobase --cluster={clusterId} update --infobase={infoBaseId} --infobase-user={user} --infobase-pwd={password} --sessions-deny=off --scheduled-jobs-deny=off"); + => StartRacAndGetOutput($"infobase --cluster={clusterId} update --infobase={infoBaseId} --infobase-user={user} --infobase-pwd={password} --sessions-deny=off --scheduled-jobs-deny=off", 10); - private List> GetOutputItems(string command) + private List> GetOutputItems(string command, int commandTimeout) { - var output = StartRacAndGetOutput(command); + var output = StartRacAndGetOutput(command, commandTimeout); var outputItems = output.Split($"{Environment.NewLine}{Environment.NewLine}", StringSplitOptions.RemoveEmptyEntries).ToList(); @@ -100,7 +86,7 @@ public class Rac(V8Platform platform, string host = "localhost", int port = 1545 .ToList(); } - private string StartRacAndGetOutput(string command) + private string StartRacAndGetOutput(string command, int commandTimeout) { if (!platform.HasRac) throw new Exception($"{platform.PlatformPath} doesn't contain 1cv8 executable"); @@ -111,20 +97,33 @@ public class Rac(V8Platform platform, string host = "localhost", int port = 1545 RedirectStandardOutput = true, RedirectStandardInput = true, RedirectStandardError = true, + CreateNoWindow = true, + UseShellExecute = false, Arguments = $"{host}:{port} {command}" }; using var process = new Process(); process.StartInfo = psi; - - if (!process.Start()) - throw new Exception($"Failed to start {psi.FileName}"); - process.WaitForExit(); + if (!process.Start()) + { + process.Close(); + throw new Exception($"Ошибка запуска {psi.FileName}"); + } + + if (process.WaitForExit(TimeSpan.FromSeconds(commandTimeout)) && process.ExitCode != 0) + { + using var errorStream = process.StandardError; + var error = errorStream.ReadToEnd(); + process.Close(); + + throw new Exception($"Ошибка выполнения команды RAC: {error}"); + } - if (process.ExitCode != 0) - throw new Exception($"Failed to execute rac command {process.StandardError.ReadToEnd()}"); + using var outputStream = process.StandardOutput; + var output = outputStream.ReadToEnd(); + process.Close(); - return process.StandardOutput.ReadToEnd(); + return output; } } \ No newline at end of file diff --git a/OneSTools.Common/Platform/V8Cluster.cs b/OneSTools.Common/Platform/RemoteAdministration/V8Cluster.cs similarity index 81% rename from OneSTools.Common/Platform/V8Cluster.cs rename to OneSTools.Common/Platform/RemoteAdministration/V8Cluster.cs index 7c3bcd9..a0d4307 100644 --- a/OneSTools.Common/Platform/V8Cluster.cs +++ b/OneSTools.Common/Platform/RemoteAdministration/V8Cluster.cs @@ -1,6 +1,6 @@ using MessagePack; -namespace OneSTools.Common.Platform; +namespace OneSTools.Common.Platform.RemoteAdministration; [MessagePackObject] public class V8Cluster diff --git a/OneSTools.Common/Platform/V8InfoBase.cs b/OneSTools.Common/Platform/RemoteAdministration/V8InfoBase.cs similarity index 86% rename from OneSTools.Common/Platform/V8InfoBase.cs rename to OneSTools.Common/Platform/RemoteAdministration/V8InfoBase.cs index ea97c18..7298b12 100644 --- a/OneSTools.Common/Platform/V8InfoBase.cs +++ b/OneSTools.Common/Platform/RemoteAdministration/V8InfoBase.cs @@ -1,6 +1,6 @@ using MessagePack; -namespace OneSTools.Common.Platform; +namespace OneSTools.Common.Platform.RemoteAdministration; [MessagePackObject] public class V8InfoBase : V8InfoBaseSummary diff --git a/OneSTools.Common/Platform/V8InfoBaseSummary.cs b/OneSTools.Common/Platform/RemoteAdministration/V8InfoBaseSummary.cs similarity index 74% rename from OneSTools.Common/Platform/V8InfoBaseSummary.cs rename to OneSTools.Common/Platform/RemoteAdministration/V8InfoBaseSummary.cs index 3a6200a..9bcd93e 100644 --- a/OneSTools.Common/Platform/V8InfoBaseSummary.cs +++ b/OneSTools.Common/Platform/RemoteAdministration/V8InfoBaseSummary.cs @@ -1,6 +1,6 @@ using MessagePack; -namespace OneSTools.Common.Platform; +namespace OneSTools.Common.Platform.RemoteAdministration; [MessagePackObject] public class V8InfoBaseSummary diff --git a/OneSTools.Common/Platform/V8Session.cs b/OneSTools.Common/Platform/RemoteAdministration/V8Session.cs similarity index 86% rename from OneSTools.Common/Platform/V8Session.cs rename to OneSTools.Common/Platform/RemoteAdministration/V8Session.cs index 3810aa0..e5c253a 100644 --- a/OneSTools.Common/Platform/V8Session.cs +++ b/OneSTools.Common/Platform/RemoteAdministration/V8Session.cs @@ -1,4 +1,4 @@ -namespace OneSTools.Common.Platform; +namespace OneSTools.Common.Platform.RemoteAdministration; public class V8Session { diff --git a/OneSTools.Common/Platform/ArgsParser.cs b/OneSTools.Common/Platform/Services/ArgsParser.cs similarity index 98% rename from OneSTools.Common/Platform/ArgsParser.cs rename to OneSTools.Common/Platform/Services/ArgsParser.cs index 6a1a7fb..623b71b 100644 --- a/OneSTools.Common/Platform/ArgsParser.cs +++ b/OneSTools.Common/Platform/Services/ArgsParser.cs @@ -1,6 +1,6 @@ using System.Text.RegularExpressions; -namespace OneSTools.Common.Platform; +namespace OneSTools.Common.Platform.Services; public record ArgsKeyValue(string Key, string Value); diff --git a/OneSTools.Common/Platform/RagentService.cs b/OneSTools.Common/Platform/Services/RagentService.cs similarity index 83% rename from OneSTools.Common/Platform/RagentService.cs rename to OneSTools.Common/Platform/Services/RagentService.cs index cc9afac..74dafce 100644 --- a/OneSTools.Common/Platform/RagentService.cs +++ b/OneSTools.Common/Platform/Services/RagentService.cs @@ -1,7 +1,8 @@ using System.ComponentModel; using MessagePack; +using OneSTools.Common.Platform.Services; -namespace OneSTools.Common.Platform; +namespace OneSTools.Common.Platform.Services; [DisplayName("Служба агента сервера 1С")] [MessagePackObject] diff --git a/OneSTools.Common/Platform/RasService.cs b/OneSTools.Common/Platform/Services/RasService.cs similarity index 88% rename from OneSTools.Common/Platform/RasService.cs rename to OneSTools.Common/Platform/Services/RasService.cs index 83552fe..c6b9df5 100644 --- a/OneSTools.Common/Platform/RasService.cs +++ b/OneSTools.Common/Platform/Services/RasService.cs @@ -1,7 +1,8 @@ using System.ComponentModel; using MessagePack; +using OneSTools.Common.Platform.Services; -namespace OneSTools.Common.Platform; +namespace OneSTools.Common.Platform.Services; [DisplayName("Служба сервера удаленного администрирования 1С")] [MessagePackObject] diff --git a/OneSTools.Common/Platform/V8Service.cs b/OneSTools.Common/Platform/Services/V8Service.cs similarity index 86% rename from OneSTools.Common/Platform/V8Service.cs rename to OneSTools.Common/Platform/Services/V8Service.cs index 9a221aa..aa2db12 100644 --- a/OneSTools.Common/Platform/V8Service.cs +++ b/OneSTools.Common/Platform/Services/V8Service.cs @@ -1,7 +1,7 @@ using System.ComponentModel; using MessagePack; -namespace OneSTools.Common.Platform; +namespace OneSTools.Common.Platform.Services; [DisplayName("Служба 1С")] [MessagePackObject] diff --git a/OneSTools.Common/Platform/V8ServiceType.cs b/OneSTools.Common/Platform/Services/V8ServiceType.cs similarity index 57% rename from OneSTools.Common/Platform/V8ServiceType.cs rename to OneSTools.Common/Platform/Services/V8ServiceType.cs index 4cad8c0..066a732 100644 --- a/OneSTools.Common/Platform/V8ServiceType.cs +++ b/OneSTools.Common/Platform/Services/V8ServiceType.cs @@ -1,4 +1,4 @@ -namespace OneSTools.Common.Platform; +namespace OneSTools.Common.Platform.Services; public enum V8ServiceType { diff --git a/OneSTools.Common/Platform/V8Services.cs b/OneSTools.Common/Platform/Services/V8Services.cs similarity index 99% rename from OneSTools.Common/Platform/V8Services.cs rename to OneSTools.Common/Platform/Services/V8Services.cs index c1fe7e0..b8c1208 100644 --- a/OneSTools.Common/Platform/V8Services.cs +++ b/OneSTools.Common/Platform/Services/V8Services.cs @@ -4,7 +4,7 @@ using System.Text.RegularExpressions; using Microsoft.Win32; using OneSTools.Common.Extensions; -namespace OneSTools.Common.Platform; +namespace OneSTools.Common.Platform.Services; public static class V8Services { diff --git a/OneSTools.Common/Platform/Unpack/BlockHeader.cs b/OneSTools.Common/Platform/Unpack/BlockHeader.cs new file mode 100644 index 0000000..ccd4ed5 --- /dev/null +++ b/OneSTools.Common/Platform/Unpack/BlockHeader.cs @@ -0,0 +1,55 @@ +namespace OneSTools.Common.Platform.Unpack; + +public struct BlockHeader( + uint dataSize = 0, + uint pageSize = FileFormat.V8DefaultPageSize, + uint nextPageAddr = FileFormat.V8FfSignature) +{ + public uint DataSize { get; } = dataSize; + public uint PageSize { get; } = pageSize; + public uint NextPageAddr { get; } = nextPageAddr; + + private static void ReadExpectedByte(Stream reader, int expectedValue) + { + if (reader.ReadByte() != expectedValue) + throw new File8FormatException(); + } + + private static uint ReadHexData(Stream reader) + { + var hex = new byte[8]; + + if (reader.Read(hex, 0, 8) < 8) + { + throw new File8FormatException(); + } + + try + { + return Convert.ToUInt32(System.Text.Encoding.ASCII.GetString(hex), 16); + } + catch + { + throw new File8FormatException(); + } + } + + public static BlockHeader Read(Stream reader) + { + ReadExpectedByte(reader, 0x0D); + ReadExpectedByte(reader, 0x0A); + + var dataSize = ReadHexData(reader); + ReadExpectedByte(reader, 0x20); + var pageSize = ReadHexData(reader); + ReadExpectedByte(reader, 0x20); + var nextPageAddr = ReadHexData(reader); + ReadExpectedByte(reader, 0x20); + + ReadExpectedByte(reader, 0x0D); + ReadExpectedByte(reader, 0x0A); + + return new BlockHeader(dataSize, pageSize, nextPageAddr); + } + +} \ No newline at end of file diff --git a/OneSTools.Common/Platform/Unpack/BlockReader.cs b/OneSTools.Common/Platform/Unpack/BlockReader.cs new file mode 100644 index 0000000..fc0c7ed --- /dev/null +++ b/OneSTools.Common/Platform/Unpack/BlockReader.cs @@ -0,0 +1,152 @@ +using System.IO.Compression; + +namespace OneSTools.Common.Platform.Unpack; + +public class BlockReader : Stream +{ + private BlockHeader _currentHeader; + private readonly Stream _reader; + private readonly int _dataSize; + + private byte[] _currentPageData; + private int _currentPageOffset; + private bool _isPacked; + private bool _isContainer; + + public BlockReader(Stream basicStream) + { + _reader = basicStream; + _currentHeader = BlockHeader.Read(_reader); + _dataSize = (int)_currentHeader.DataSize; + ReadPage(); + AnalyzeState(); + } + + private void ReadPage() + { + var currentDataSize = Math.Min(_dataSize, (int)_currentHeader.PageSize); + _currentPageData = new byte[currentDataSize]; + _reader.Read(_currentPageData, 0, currentDataSize); + _currentPageOffset = 0; + } + + private void AnalyzeState() + { + var bufferToCheck = _currentPageData; + + try + { + using var inputStream = new MemoryStream(bufferToCheck); + using var deflateStream = new DeflateStream(inputStream, CompressionMode.Decompress); + + using var outputStream = new MemoryStream(); + deflateStream.CopyTo(outputStream); + + var tmp = outputStream.ToArray(); + _isPacked = true; + bufferToCheck = tmp; + } + catch + { + _isPacked = false; + } + + _isContainer = FileFormat.IsContainer(bufferToCheck); + } + + private void MoveNextBlock() + { + if (_currentHeader.NextPageAddr == FileFormat.V8FfSignature) + { + _currentPageData = null; + return; + } + _reader.Seek(_currentHeader.NextPageAddr, SeekOrigin.Begin); + _currentHeader = BlockHeader.Read(_reader); + ReadPage(); + } + + public bool IsPacked => _isPacked; + + public bool IsContainer => _isContainer; + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => _dataSize; + + public override long Position + { + get => throw new NotSupportedException(); + + set => throw new NotSupportedException(); + } + + public override void Flush() + { + throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (_currentPageData == null) + { + return 0; + } + + var bytesRead = 0; + var countLeft = count; + + while (countLeft > 0) + { + var leftInPage = _currentPageData.Length - _currentPageOffset; + + if (leftInPage == 0) + { + MoveNextBlock(); + + if (_currentPageData == null) + { + break; + } + } + + var readFromCurrentPage = Math.Min(leftInPage, countLeft); + + Buffer.BlockCopy(_currentPageData, _currentPageOffset, buffer, offset, readFromCurrentPage); + _currentPageOffset += readFromCurrentPage; + offset += readFromCurrentPage; + + bytesRead += readFromCurrentPage; + countLeft -= readFromCurrentPage; + } + + return bytesRead; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public static byte[] ReadDataBlock(Stream reader) + { + var blockReader = new BlockReader(reader); + var buf = new byte[blockReader.Length]; + blockReader.ReadExactly(buf, 0, buf.Length); + return buf; + } +} \ No newline at end of file diff --git a/OneSTools.Common/Platform/Unpack/ContainerHeader.cs b/OneSTools.Common/Platform/Unpack/ContainerHeader.cs new file mode 100644 index 0000000..2d3f317 --- /dev/null +++ b/OneSTools.Common/Platform/Unpack/ContainerHeader.cs @@ -0,0 +1,34 @@ +namespace OneSTools.Common.Platform.Unpack; + +public struct ContainerHeader +{ + public readonly uint NextPageAddr; + public readonly uint PageSize; + public readonly uint StorageVer; + public readonly uint Reserved; + + private ContainerHeader(uint nextPageAddr = FileFormat.V8FfSignature, uint pageSize = FileFormat.V8DefaultPageSize, uint storageVer = 0, uint reserved = 0) + { + NextPageAddr = nextPageAddr; + PageSize = pageSize; + StorageVer = 0; + Reserved = 0; + } + + public static ContainerHeader Read(Stream reader) + { + const int headerSize = 16; + var buf = new byte[headerSize]; + if (reader.Read(buf, 0, headerSize) < headerSize) + { + throw new File8FormatException(); + } + + return new ContainerHeader( + nextPageAddr: BitConverter.ToUInt32(buf, 0), + pageSize: BitConverter.ToUInt32(buf, 4), + storageVer: BitConverter.ToUInt32(buf, 8), + reserved: BitConverter.ToUInt32(buf, 12) + ); + } +} \ No newline at end of file diff --git a/OneSTools.Common/Platform/Unpack/ElementAddress.cs b/OneSTools.Common/Platform/Unpack/ElementAddress.cs new file mode 100644 index 0000000..4be21dd --- /dev/null +++ b/OneSTools.Common/Platform/Unpack/ElementAddress.cs @@ -0,0 +1,28 @@ +namespace OneSTools.Common.Platform.Unpack; + +public readonly struct ElementAddress(uint headerAddress, uint dataAddress, uint signature = FileFormat.V8FfSignature) +{ + public uint HeaderAddress { get; } = headerAddress; + public uint DataAddress { get; } = dataAddress; + public uint Signature { get; } = signature; + + public static IList Parse(byte[] buf) + { + const int elementSize = 4 + 4 + 4; + var result = new List(); + + for (var offset = 0; offset + elementSize <= buf.Length; offset += elementSize) + { + var headerAddress = BitConverter.ToUInt32(buf, offset); + var dataAddress = BitConverter.ToUInt32(buf, offset + 4); + var signature = BitConverter.ToUInt32(buf, offset + 8); + + result.Add(new ElementAddress(headerAddress, dataAddress, signature)); + } + + return result; + } + + public override string ToString() + => $"{HeaderAddress:x8}:{DataAddress:x8}:{Signature:x8}"; +} \ No newline at end of file diff --git a/OneSTools.Common/Platform/Unpack/ElementHeader.cs b/OneSTools.Common/Platform/Unpack/ElementHeader.cs new file mode 100644 index 0000000..f4e7573 --- /dev/null +++ b/OneSTools.Common/Platform/Unpack/ElementHeader.cs @@ -0,0 +1,29 @@ +namespace OneSTools.Common.Platform.Unpack; + +public struct ElementHeader(string name, DateTime creationDate, DateTime modificationDate) +{ + public readonly DateTime CreationDate = creationDate; + public readonly DateTime ModificationDate = modificationDate; + public readonly string Name = name; + + public static DateTime File8Date(ulong serializedDate) + { + return new DateTime((long) serializedDate * 1000); + } + + public static ElementHeader Parse(byte[] buf) + { + var serializedCreationDate = BitConverter.ToUInt64(buf, 0); + var serializedModificationDate = BitConverter.ToUInt64(buf, 8); + // 4 байта на Reserved + var enc = new System.Text.UnicodeEncoding(bigEndian: false, byteOrderMark: false); + + const int nameOffset = 8 + 8 + 4; + var name = enc.GetString(buf, nameOffset, buf.Length - nameOffset - 4).TrimEnd('\0'); + + var creationDate = File8Date(serializedCreationDate); + var modificationDate = File8Date(serializedModificationDate); + + return new ElementHeader(name, creationDate, modificationDate); + } +} \ No newline at end of file diff --git a/OneSTools.Common/Platform/Unpack/File8.cs b/OneSTools.Common/Platform/Unpack/File8.cs new file mode 100644 index 0000000..97b5a8a --- /dev/null +++ b/OneSTools.Common/Platform/Unpack/File8.cs @@ -0,0 +1,18 @@ +namespace OneSTools.Common.Platform.Unpack; + +public class File8 +{ + internal File8(ElementHeader header, uint dataOffset) + { + DataOffset = (int)dataOffset; + + Name = header.Name; + ModificationTime = header.ModificationDate; + CreationTime = header.CreationDate; + } + + public string Name { get; } + public DateTime ModificationTime { get; } + public DateTime CreationTime { get; } + public int DataOffset { get; } +} \ No newline at end of file diff --git a/OneSTools.Common/Platform/Unpack/File8Collection.cs b/OneSTools.Common/Platform/Unpack/File8Collection.cs new file mode 100644 index 0000000..0314cac --- /dev/null +++ b/OneSTools.Common/Platform/Unpack/File8Collection.cs @@ -0,0 +1,45 @@ +using System.Collections; + +namespace OneSTools.Common.Platform.Unpack; + +public class File8Collection : IEnumerable +{ + private readonly IReadOnlyList _data; + + public File8Collection(IEnumerable data) + { + var fileList = new List(); + fileList.AddRange(data); + _data = fileList; + } + + public int Count() + { + return _data.Count; + } + + public File8? Get(int index) + { + return _data[index]; + } + + public File8? Get(string name) + { + return _data.First(f => f != null && f.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); + } + + public File8? Find(string name) + { + return _data.FirstOrDefault(f => f != null && f.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); + } + + public IEnumerator GetEnumerator() + { + return _data.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} \ No newline at end of file diff --git a/OneSTools.Common/Platform/Unpack/File8FormatException.cs b/OneSTools.Common/Platform/Unpack/File8FormatException.cs new file mode 100644 index 0000000..3aaff2c --- /dev/null +++ b/OneSTools.Common/Platform/Unpack/File8FormatException.cs @@ -0,0 +1,3 @@ +namespace OneSTools.Common.Platform.Unpack; + +public class File8FormatException : Exception; \ No newline at end of file diff --git a/OneSTools.Common/Platform/Unpack/File8Reader.cs b/OneSTools.Common/Platform/Unpack/File8Reader.cs new file mode 100644 index 0000000..2f83790 --- /dev/null +++ b/OneSTools.Common/Platform/Unpack/File8Reader.cs @@ -0,0 +1,138 @@ +using System.IO.Compression; +using System.Reflection.Metadata; + +namespace OneSTools.Common.Platform.Unpack; + +public class File8Reader : IDisposable +{ + private readonly Stream _reader; + private readonly bool _dataPacked; + private int _storageVersion; + + public decimal StorageVersion => _storageVersion; + public File8Collection Elements { get; } + + public File8Reader(string filePath, bool dataPacked = true) + { + const int magicSize = 100 * 1024; + var fileStream = new FileStream(filePath, FileMode.Open); + if (fileStream.Length >= magicSize) + _reader = fileStream; + else + { + var memoryStream = new MemoryStream(); + fileStream.CopyTo(memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + + _reader = memoryStream; + } + + _dataPacked = dataPacked; + var fileList = ReadFileList(); + Elements = new File8Collection(fileList); + } + + public File8Reader(Stream stream, bool dataPacked = true) + { + _reader = stream; + _dataPacked = dataPacked; + var fileList = ReadFileList(); + Elements = new File8Collection(fileList); + } + + private List ReadFileList() + { + var containerHeader = ContainerHeader.Read(_reader); + _storageVersion = (int)containerHeader.StorageVer; + var elemsAddrBuf = BlockReader.ReadDataBlock(_reader); + var addresses = ElementAddress.Parse(elemsAddrBuf); + + var fileList = new List(); + foreach (var address in addresses) + { + if (address.HeaderAddress == FileFormat.V8FfSignature || address.Signature != FileFormat.V8FfSignature) + continue; + + _reader.Seek(address.HeaderAddress, SeekOrigin.Begin); + var buf = BlockReader.ReadDataBlock(_reader); + + var fileHeader = ElementHeader.Parse(buf); + fileList.Add(new File8(fileHeader, address.DataAddress)); + } + + return fileList; + } + + public void Extract(File8 element, string destDir, bool recursiveUnpack = false) + { + + if (!Directory.Exists(destDir)) + { + Directory.CreateDirectory(destDir); + } + + Stream fileExtractor; + + if (element.DataOffset == FileFormat.V8FfSignature) + { + // Файл есть, но пуст + fileExtractor = new MemoryStream(); + } + else + { + _reader.Seek(element.DataOffset, SeekOrigin.Begin); + + var blockExtractor = new BlockReader(_reader); + if (blockExtractor.IsPacked && _dataPacked) + { + fileExtractor = new DeflateStream(blockExtractor, CompressionMode.Decompress); + } + else + { + fileExtractor = blockExtractor; + } + + if (blockExtractor.IsContainer && recursiveUnpack) + { + var outputDirectory = Path.Combine(destDir, element.Name); + var tmpData = new MemoryStream(); // TODO: переделать MemoryStream --> FileStream + fileExtractor.CopyTo(tmpData); + tmpData.Seek(0, SeekOrigin.Begin); + + var internalContainer = new File8Reader(tmpData, dataPacked: false); + internalContainer.ExtractAll(outputDirectory, recursiveUnpack); + + return; + } + } + + // Просто файл + var outputFileName = Path.Combine(destDir, element.Name); + using var outputFile = new FileStream(outputFileName, FileMode.Create); + fileExtractor.CopyTo(outputFile); + } + + /// + /// Извлекает все файлы из контейнера. + /// + /// Каталог назначения. + /// Если установлен в Истина, то все найденные вложенные восьмофайлы + /// будут распакованы в отдельные подкаталоги. Необязательный. + public void ExtractAll(string destDir, bool recursiveUnpack = false) + { + foreach (var element in Elements) + { + Extract(element, destDir, recursiveUnpack); + } + } + + public void Dispose() + { + _reader.Close(); + } + + public void Close() + { + _reader.Close(); + } +} \ No newline at end of file diff --git a/OneSTools.Common/Platform/Unpack/FileFormat.cs b/OneSTools.Common/Platform/Unpack/FileFormat.cs new file mode 100644 index 0000000..4241c56 --- /dev/null +++ b/OneSTools.Common/Platform/Unpack/FileFormat.cs @@ -0,0 +1,23 @@ +namespace OneSTools.Common.Platform.Unpack; + +internal static partial class FileFormat +{ + public const uint V8FfSignature = 0x7fffffff; + public const uint V8DefaultPageSize = 512; + + public static bool IsContainer(byte[] data) + { + var reader = new MemoryStream(data); + try + { + ContainerHeader.Read(reader); + BlockHeader.Read(reader); + } + catch (File8FormatException) + { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/onecmonitor-agent/Asserts/ПодтверждениеЛегальности.epf b/onecmonitor-agent/Asserts/ПодтверждениеЛегальности.epf new file mode 100644 index 0000000000000000000000000000000000000000..4945e2a9949294e0f848d165ab1e991b561be3c2 GIT binary patch literal 6661 zcmeHLXHXPb*KR}@qGUwL8HqE)zz~(JAX$(c2Zo#^Dsd1HkQ^l=!I2fc-*C z1>GyMP@X*rheCOf=>5V&OC<#kZ35dacv%e@0Em+B>)PP;kHJQ?FHix1JN3REOb-AZ zU?bXR7y*ETXM0pJ7Ji29FJ{44&Z9{2+uh<$iKWf4EagZ^}{^*4<} zz=0$_1q?ud)d4_*RUSYB65yE(;0W4@njGK+Rtc~l37)}0O%9Z@;0P2biG7aXRm4$} z0CdkOKb8P@a`=HzQ27a=MOml!^kFl-kt6?$)3Vg+$0@IR+X{GSB-;9}Tg# zhpMVq4i7WKv9K<*7gXzWBd6`$BWLl~nbMCR;+`}q$i~XeXz^PN1mOo=XiY!TKL2=! zvhPwUMDyy?lztr+CS)6uywcjk;FZ*ScT8;KgUs zS(M2ZN1YCzai>5zs_2*;MV}E8tubR~rBEE!Ey}^{(zQjjeP2Jha5UgsX`Y+7DaNwO`i?%)sOqVajch|*t-id`H?;4p=U~T^ zl-yT_P*2r6Q=PN-<&Te=&->S0)v4l7*7{_fXRMoKVjVhdcFSCBRsRv^q-7S~(@Gm1 zP14aMwc;**9y{bHh|?p@)UDA*J0&Ev9%{}X6udbBpZ4YxI$|+DbWTnFw->(Oi>Jsv zirO0zZ?I8^xf@W58`Cg;9A^qY89b5|W{`2J=_^h(s6xK6*8*0Rht+SV!ChZ)Qn*yk z$P4YcZ6-fe?~R}17c?x94y_>Ey_lGJ)2SfXv9AAk^7(=ITV9*w4~uWs#`NVdh1i9+ z84Z~{)g~E(vL^@_xE~AoUTdGAGyOB4B>R5uGG_cGZsMIA%aPMLJ~8BOIbKsYByAI^KD_Ev0F9(e^$oB3acg6{?V7 zJGJ{`4|=oAv#YNws=4!Z$&lUc(qhEpoPwv?>_!&fZeI+yOI=7^T)1SOx;S~MX=D6* z>bh6au-(%9=Jpd4gQ@J!2fGbBQkydh5AwZ`-rwa4q)2TkcjH#KmWMuT8M-&};7&YB z)S!4ckAXa8yY@jqxxq)}v!u;Qdcw6&WZmb~RS@3T*Sh)P$dYSKFP2ZM#tMgu8{V{n zVFgOF_)A}2X7d=eu&u~wFL^mlbMIOu7i2V^?2g7M^GZL;2_c`&k?w->wPKez+m~(X zgBvFym_Sm((^%J!&03S|VCMWgl)pe{n!vd|bRJ0Jl2IVih&h3XI@!H^fCRDT1nR`h z00Vo7If95*c@T}n9DxEYPT-L!5#U&7um`>O^ar<>NBwf&l5NmU+XJQ9Hv7c*f&FX{ zV?Mf2Ko6oa7t05uA^i%;X6X^ARmmTPFmih?1}GHWT3!4u*Z8rW+$7J^NG4q)?KbCl zm+Ks&6g}WtBp86f(wq0YE(q}0t*j_5gm$P+#q=_C%sWP3s9(c=g?I{zu(;3)nniI^ z$RdU_{n^6eZ@zbZCRU!7G^JP`uH6&;+%71O;9W%6={xa&1fud&_`YSbsQ*rj`S+PK7a{Z6qK3=MA$|Hw-zR(2 zq7*PfyseQP@3kmgkudG^XT+_Vja92(owD&7pV3?hJ|ne?k9pzRC>Sre4g5l@V~z^W)G9u4!4iU?Y@5`YN}Zy^q)Y zqC)gCR2qJk`uIIHh8t0aFCs+VRFDsQ#j7cO#}7rs_lGPLZq2HBEpJJ^3HMp*7(#Fb zhKIE)3`UmnnZh$xyrLg1+5_D1Nll+}SvaAT`8|Aqwu%+|u9laOd8Z?xfqQ-dhY$Ms;2LM=N()Jv^AU7G>ohBA0zyXC(~**!&4=XIvBrE zUpp!6y20Uwd$wbb___?q*~ICI*a>9Lj2z`&Ofyj#Ll}YZIo&!(uq;bbphR3I~wsG!Qrfd_m zVc(@l7_#%zAbRrX@%#k@Nz=8@ua*Vrcvx>_6=QA(3d_upJWDAWgP>2eqa7+ynG;>w zEDGeqA>W@Gqr6*QHyVGkzh0#3-8kvk0Qj-5P}D9k=PKJ*SyqYByU!z3rt!!Vwa4zx zSu8Jg@uc^v3wPHZq4yUHoF6=?!xqjdz4*T4Rq%!5D*Ea|XhV4{QUX33y%fW1*Gqjn zwpm10oHbYxruNz`y8tQO2frY8@r|%!yay2 z#a9OMI8zJa`N)O?x-isa8fm3xFr(3}jF$F6l)rI&;C@0_-^`4ZoVDVZsJT_p%g4%~ z6XkFxD({$%8sr%2=3^z}4{tlhl4?$dPPvm0a{#eQ^K%>&$;SEGuLY0E63Wb!^-~Eq zux|C-mvEU8_z2g0|fjDU2vygqARpB zG_pBe-!VVE(WuO!tS&vzxvWlG$Fj+rF%8!Y{uyb~m1%(L!Su(gz&~aGmVcTD{y>-P zK@5}qD|7{cUn~~@e=vsmfKiNC4km&9`n!dQF&Qbd=Je}%DVQT^4i5I7PGu;=fdMkL zf0{f0eV$)<-~b-~k39KjjQQ_P`ePaxLjISLEICa%@fwUqV0qD}~j z(M#)~&=?^p=_!R1a{P1fo6(it6`EnRPE+Kn25G@0LG7QBx;G}~x|54ILatpde9^EH zE$i5RbLWoLCxOBu#oqZxJj>#vzE^@YKYNwam0QYQabrKcT1=v*W$#VVfnU;)zF)o1|FAXZxY_UmnjZN zS}-@DEjo2;o=#t}sXtj8W*9iJ@?B|G=RNE_K_xAHWvs2I42BtR>MW9c+#J^F^F?aJ zqbd;<^wQ1GzYhzq_2cwSf2U$CvLf}nZFSA(*Cr$*rjOr~M(); - services.AddSingleton(); services.AddDbContext(); + + // Commands watcher connection services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddHostedService(); + + services.AddSingleton(); + services.AddHostedService(); + services.AddHostedService(); }) .Build(); diff --git a/onecmonitor-agent/Services/CommandsWatcher.cs b/onecmonitor-agent/Services/CommandsWatcher.cs index 1eb34a5..e2491f8 100644 --- a/onecmonitor-agent/Services/CommandsWatcher.cs +++ b/onecmonitor-agent/Services/CommandsWatcher.cs @@ -4,6 +4,8 @@ using OnecMonitor.Agent.Services.InfoBases; using OnecMonitor.Agent.Services.TechLog; using OnecMonitor.Common.DTO; using OneSTools.Common.Platform; +using OneSTools.Common.Platform.RemoteAdministration; +using OneSTools.Common.Platform.Services; namespace OnecMonitor.Agent.Services { @@ -11,14 +13,14 @@ namespace OnecMonitor.Agent.Services { private readonly OnecMonitorConnection _server; private readonly AppDbContext _appDbContext; - private readonly InfoBasesUpdater _infoBasesUpdater; + private readonly InfoBasesUpdateTasksQueue _updateTasksQueue; private readonly RasHolder _rasHolder; private readonly TechLogExporter _techLogExporter; private readonly ILogger _logger; public CommandsWatcher( IServiceProvider serviceProvider, - InfoBasesUpdater infoBasesUpdater, + InfoBasesUpdateTasksQueue updateTasksQueue, TechLogExporter techLogExporter, RasHolder rasHolder, ILogger logger) @@ -28,7 +30,7 @@ namespace OnecMonitor.Agent.Services _server = scope.ServiceProvider.GetRequiredService(); _appDbContext = scope.ServiceProvider.GetRequiredService(); _techLogExporter = techLogExporter; - _infoBasesUpdater = infoBasesUpdater; + _updateTasksQueue = updateTasksQueue; _logger = logger; } @@ -92,22 +94,19 @@ namespace OnecMonitor.Agent.Services private async Task SendInstalledPlatforms(Message message, CancellationToken cancellationToken) { - _logger.LogTrace("Send installed platforms"); - var platforms = V8Platforms.GetInstalledPlatforms(); await _server.Send(MessageType.InstalledPlatforms, platforms, message, cancellationToken); } private async Task HandleUpdateInfoBasesRequest(Message message, CancellationToken cancellationToken) { + await _updateTasksQueue.QueueAsync(message, cancellationToken); await _server.SendOk(message, cancellationToken); - _infoBasesUpdater.RequestInfoBasesUpdateTask(); } private async Task HandleUpdateSettingsRequest(Message message, CancellationToken cancellationToken) { await _server.SendOk(message, cancellationToken); - await UpdateSettings(cancellationToken); } @@ -127,8 +126,6 @@ namespace OnecMonitor.Agent.Services private async Task SendV8Clusters(Message message, CancellationToken cancellationToken) { - _logger.LogTrace("Send clusters"); - var ragents = V8Services.GetActiveRagentServices(); var clusters = new List(); @@ -140,8 +137,6 @@ namespace OnecMonitor.Agent.Services private async Task SendV8InfoBases(Message message, CancellationToken cancellationToken) { - _logger.LogTrace("Send infobases"); - var request = MessagePackSerializer.Deserialize(message.Data, cancellationToken: cancellationToken); var ragent = V8Services.GetActiveRagentForClusterPort(request.Cluster.Port); @@ -156,23 +151,18 @@ namespace OnecMonitor.Agent.Services private async Task SendRagentServices(Message message, CancellationToken cancellationToken) { - _logger.LogTrace("Send ragent services"); - var services = V8Services.GetRagentServices(); await _server.Send(MessageType.RagentServices, services, message, cancellationToken); } private async Task SendRasServices(Message message, CancellationToken cancellationToken) { - _logger.LogTrace("Send ras services"); - var services = _rasHolder.GetRasServices(); await _server.Send(MessageType.RasServices, services, message, cancellationToken); } private async Task UpdateTechLogSeancesByRequest(Message message, CancellationToken cancellationToken) { - _logger.LogTrace("Updating tech log seances by server request"); await _server.SendOk(message, cancellationToken); if (_techLogExporter.Enabled) @@ -181,14 +171,12 @@ namespace OnecMonitor.Agent.Services private async Task UpdateTechLogSeances(CancellationToken cancellationToken) { - _logger.LogTrace("Updating tech log seances"); - try { var seances = await _server.Get>( MessageType.TechLogSeancesRequest, MessageType.TechLogSeances, - cancellationToken);; + cancellationToken); await _appDbContext.Database.BeginTransactionAsync(cancellationToken); @@ -223,8 +211,6 @@ namespace OnecMonitor.Agent.Services await _appDbContext.Database.CommitTransactionAsync(cancellationToken); await _appDbContext.SaveChangesAsync(cancellationToken); - - _logger.LogTrace("Tech log seances updated"); } catch (Exception ex) { diff --git a/onecmonitor-agent/Services/InfoBases/InfoBasesUpdateTasksQueue.cs b/onecmonitor-agent/Services/InfoBases/InfoBasesUpdateTasksQueue.cs new file mode 100644 index 0000000..1cc8a98 --- /dev/null +++ b/onecmonitor-agent/Services/InfoBases/InfoBasesUpdateTasksQueue.cs @@ -0,0 +1,15 @@ +using System.Threading.Channels; +using OnecMonitor.Common.DTO; + +namespace OnecMonitor.Agent.Services.InfoBases; + +public class InfoBasesUpdateTasksQueue +{ + private readonly Channel _updateRequestsChannel = Channel.CreateUnbounded(); + + public async Task QueueAsync(Message message, CancellationToken cancellationToken) + => await _updateRequestsChannel.Writer.WriteAsync(message, cancellationToken); + + public async Task DequeueAsync(CancellationToken cancellationToken) + => await _updateRequestsChannel.Reader.ReadAsync(cancellationToken); +} \ No newline at end of file diff --git a/onecmonitor-agent/Services/InfoBases/InfoBasesUpdater.cs b/onecmonitor-agent/Services/InfoBases/InfoBasesUpdater.cs index 21fbadd..a350472 100644 --- a/onecmonitor-agent/Services/InfoBases/InfoBasesUpdater.cs +++ b/onecmonitor-agent/Services/InfoBases/InfoBasesUpdater.cs @@ -1,49 +1,73 @@ using System.Reflection; -using System.Text; -using OnecMonitor.Agent.Extensions; +using System.Threading.Channels; using OnecMonitor.Common.DTO; using OneSTools.Common.Designer.Batch; -using OneSTools.Common.Extensions; -using OneSTools.Common.Platform; -using Org.BouncyCastle.Asn1.X509; +using OneSTools.Common.Platform.RemoteAdministration; +using OneSTools.Common.Platform.Services; namespace OnecMonitor.Agent.Services.InfoBases; -public sealed class InfoBasesUpdater : IDisposable +public sealed class InfoBasesUpdater : BackgroundService { + private readonly InfoBasesUpdateTasksQueue _queue; + private readonly AsyncServiceScope _scope; private readonly OnecMonitorConnection _server; private readonly IHostApplicationLifetime _applicationLifetime; private readonly RasHolder _rasHolder; private readonly ILogger _logger; private bool _disposed; + private readonly object _racLocker = new(); - public InfoBasesUpdater(IServiceProvider serviceProvider, RasHolder rasHolder, IHostApplicationLifetime applicationLifetime, ILogger logger) + public InfoBasesUpdater( + IServiceProvider serviceProvider, + InfoBasesUpdateTasksQueue tasksQueue, + RasHolder rasHolder, + IHostApplicationLifetime applicationLifetime, + ILogger logger) { _scope = serviceProvider.CreateAsyncScope(); + _queue = tasksQueue; _server = _scope.ServiceProvider.GetRequiredService(); _applicationLifetime = applicationLifetime; _rasHolder = rasHolder; _logger = logger; } - - public void RequestInfoBasesUpdateTask() + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - Task.Run(async () => + while (!stoppingToken.IsCancellationRequested) { - var accessCode = "12345"; - var message = "Технические работы"; - + await _queue.DequeueAsync(stoppingToken); + + try + { + await RequestInfoBasesUpdateTask(stoppingToken); + } + catch (Exception e) + { + _logger.LogError(e, $"Ошибка обработки задания обновления: {e.Message}"); + } + } + } + + private async Task RequestInfoBasesUpdateTask(CancellationToken cancellationToken) + { + try + { + const string accessCode = "12345"; + const string message = "Технические работы"; + var task = await _server.Get( MessageType.UpdateInfoBasesTaskRequest, MessageType.UpdateInfoBasesTask, _applicationLifetime.ApplicationStopping); - - var config = task.Configurations.FirstOrDefault(c => c.IsUpdate || c.IsConfiguration); - var extensions = task.Configurations.Where(c => c.IsExtension).ToList(); + + var config = task.Files.FirstOrDefault(c => c.IsUpdate || c.IsConfiguration); + var extensions = task.Files.Where(c => c.IsExtension).ToList(); var configurationsPaths = new Dictionary(); - task.Configurations.ForEach(i => + task.Files.ForEach(i => { var path = Path.Join(Path.GetTempPath(), $"{i.Id}.cfu") ; @@ -53,131 +77,159 @@ public sealed class InfoBasesUpdater : IDisposable file.Write(i.Data); file.Close(); } - + configurationsPaths.Add(i.Id.ToString(), path); }); - - await Parallel.ForEachAsync(task.InfoBases, async (infoBase, cancellationToken) => - { - var log = new List(); - try + var tasks = new List(); + + foreach (var infoBase in task.InfoBases) + { + var ibTask = Task.Factory.StartNew(async () => { - var ragent = V8Services.GetActiveRagentForClusterPort(infoBase.Cluster.Port); - var ras = _rasHolder.GetActiveRasForRagent(ragent); - var rac = Rac.GetRacForRasService(ras); - - var platform = ragent.Platform; - - if (!platform.HasOnecV8) - throw new Exception("Для платформы агента не установлен конфигуратор"); - - OnecV8BatchMode GetBatchDesigner() - => new(platform, $"{infoBase.Cluster.Host}:{infoBase.Cluster.Port}", infoBase.InfoBaseName); - - OnecV8BatchMode GetBatchEnterprise() - => new(platform, $"{infoBase.Cluster.Host}:{infoBase.Cluster.Port}", infoBase.InfoBaseName, false); - - await AddLogItemAndSend(task, infoBase, log, "Блокировка соединений и регламентных заданий", cancellationToken); - - rac.BlockConnections( - infoBase.Cluster.Id, - infoBase.InfoBaseInternalId.ToString(), - infoBase.Credentials.User, - infoBase.Credentials.Password, - accessCode, - message); - - await AddLogItemAndSend(task, infoBase, log, "Завершение сессий", cancellationToken); - - var sessions = rac.GetInfoBaseSessions(infoBase.Cluster.Id, infoBase.InfoBaseInternalId.ToString()); - sessions - .Where(c => c.AppId != "RAS") - .ToList() - .ForEach(s => rac.TerminateSession(infoBase.Cluster.Id, s.Id)); - - if (config != null) + var log = new List(); + + try { - if (config.IsConfiguration) - { - await AddLogItemAndSend(task, infoBase, log, "Загрузка файла конфигурации", cancellationToken); + var ragent = V8Services.GetActiveRagentForClusterPort(infoBase.Cluster.Port); + var ras = _rasHolder.GetActiveRasForRagent(ragent); + var rac = Rac.GetRacForRasService(ras); + + var platform = ragent.Platform; - using var loadCfgBatch = GetBatchDesigner(); - loadCfgBatch.LoadConfiguration( - configurationsPaths[config.Id.ToString()], + if (!platform.HasOnecV8) + throw new Exception("Для платформы агента не установлен конфигуратор"); + + OnecV8BatchMode GetBatchDesigner() + => new(platform, $"{infoBase.Cluster.Host}:{infoBase.Cluster.Port}", infoBase.InfoBaseName); + + OnecV8BatchMode GetBatchEnterprise() + => new(platform, $"{infoBase.Cluster.Host}:{infoBase.Cluster.Port}", infoBase.InfoBaseName, false); + + await AddLogItemAndSend(task, infoBase, log, "Блокировка соединений и регламентных заданий", cancellationToken); + + lock (_racLocker) + rac.BlockConnections( + infoBase.Cluster.Id, + infoBase.InfoBaseInternalId, + infoBase.Credentials.User, + infoBase.Credentials.Password, + accessCode, + message); + + await AddLogItemAndSend(task, infoBase, log, "Завершение сессий", cancellationToken); + + lock (_racLocker) + { + var sessions = rac.GetInfoBaseSessions(infoBase.Cluster.Id, infoBase.InfoBaseInternalId); + sessions + .Where(c => !c.AppId.Contains("RAS", StringComparison.CurrentCultureIgnoreCase)) + .ToList() + .ForEach(s => + { + try + { + rac.TerminateSession(infoBase.Cluster.Id, s.Id); + } + catch + { + // Игнорируем, т.к. сеанс уже мог быть закрыт, мог быть повисшим и т.п. + } + }); + } + + if (config != null) + { + if (config.IsConfiguration) + { + await AddLogItemAndSend(task, infoBase, log, "Загрузка файла конфигурации", cancellationToken); + + using var loadCfgBatch = GetBatchDesigner(); + loadCfgBatch.LoadConfiguration( + configurationsPaths[config.Id.ToString()], + infoBase.Credentials.User, + infoBase.Credentials.Password, + accessCode, + true); + + await AddLogItemAndSend(task, infoBase, log, loadCfgBatch.OutFileContent, cancellationToken); + } + else + { + await AddLogItemAndSend(task, infoBase, log, "Обновление ИБ файлом обновления конфигурации", cancellationToken); + + using var updateCfgBatch = GetBatchDesigner(); + updateCfgBatch.UpdateConfiguration( + configurationsPaths[config.Id.ToString()], + infoBase.Credentials.User, + infoBase.Credentials.Password, + accessCode, + true); + + await AddLogItemAndSend(task, infoBase, log, updateCfgBatch.OutFileContent, cancellationToken); + } + } + + if (extensions.Count > 0) + await AddLogItemAndSend(task, infoBase, log, "Загрузка расширений ИБ", cancellationToken); + + foreach (var extension in extensions) + { + await AddLogItemAndSend(task, infoBase, log, $"Загрузка расширения {extension.Name}", cancellationToken); + + using var loadExtBatch = GetBatchDesigner(); + loadExtBatch.LoadExtension( + extension.Name, + configurationsPaths[extension.Id.ToString()], infoBase.Credentials.User, infoBase.Credentials.Password, accessCode, true); - - await AddLogItemAndSend(task, infoBase, log, loadCfgBatch.OutFileContent, cancellationToken); + + await AddLogItemAndSend(task, infoBase, log, loadExtBatch.OutFileContent, cancellationToken); } - else + + var needAcceptLegalUsing = config != null; + if (needAcceptLegalUsing) { - await AddLogItemAndSend(task, infoBase, log, "Обновление ИБ файлом обновления конфигурации", cancellationToken); - - using var updateCfgBatch = GetBatchDesigner(); - updateCfgBatch.UpdateConfiguration( - configurationsPaths[config.Id.ToString()], + await AddLogItemAndSend(task, infoBase, log, "Подтверждение легальности получения и запуск обработчиков обновления", cancellationToken); + + var acceptLegalBatch = GetBatchEnterprise(); + var epfPath = GetExternalDataProcessorPath("ПодтверждениеЛегальности.epf"); + acceptLegalBatch.ExecuteExternalDataProcessor( + epfPath, infoBase.Credentials.User, infoBase.Credentials.Password, accessCode, true); - - await AddLogItemAndSend(task, infoBase, log, updateCfgBatch.OutFileContent, cancellationToken); } + + await AddLogItemAndSend(task, infoBase, log, "Разблокировка соединений и регламентных заданий", cancellationToken); + + lock (_racLocker) + rac.UnblockConnections( + infoBase.Cluster.Id, + infoBase.InfoBaseInternalId, + infoBase.Credentials.User, + infoBase.Credentials.Password); + + await AddLogItemAndSend(task, infoBase, log, "Обновление завершено", _applicationLifetime.ApplicationStopping, false, true); } - - if (extensions.Count > 0) - await AddLogItemAndSend(task, infoBase, log, "Загрузка расширений ИБ", cancellationToken); - - foreach (var extension in extensions) + catch (Exception e) { - await AddLogItemAndSend(task, infoBase, log, $"Загрузка расширения {extension.Name}", cancellationToken); - - using var loadExtBatch = GetBatchDesigner(); - loadExtBatch.LoadExtension( - extension.Name, - configurationsPaths[extension.Id.ToString()], - infoBase.Credentials.User, - infoBase.Credentials.Password, - accessCode, - true); - - await AddLogItemAndSend(task, infoBase, log, loadExtBatch.OutFileContent, cancellationToken); + await AddLogItemAndSend(task, infoBase, log, e.ToString(), _applicationLifetime.ApplicationStopping, true, true); } + }, _applicationLifetime.ApplicationStopping); + + tasks.Add(ibTask); + } - var needAcceptLegalUsing = config != null; - if (needAcceptLegalUsing) - { - await AddLogItemAndSend(task, infoBase, log, "Подтверждение легальности получения и запуск обработчиков обновления", cancellationToken); - - var acceptLegalBatch = GetBatchEnterprise(); - var epfPath = GetExternalDataProcessorPath("ПодтверждениеЛегальности.epf"); - acceptLegalBatch.ExecuteExternalDataProcessor( - epfPath, - infoBase.Credentials.User, - infoBase.Credentials.Password, - accessCode, - true); - } - - await AddLogItemAndSend(task, infoBase, log, "Разблокировка соединений и регламентных заданий", cancellationToken); - - rac.UnblockConnections( - infoBase.Cluster.Id, - infoBase.InfoBaseInternalId.ToString(), - infoBase.Credentials.User, - infoBase.Credentials.Password); - - await AddLogItemAndSend(task, infoBase, log, "Обновление завершено", cancellationToken, false, true); - } - catch (Exception e) - { - await AddLogItemAndSend(task, infoBase, log, e.ToString(), cancellationToken, true, true); - } - }); - }); + Task.WaitAll(tasks.ToArray(), cancellationToken); + } + catch (Exception e) + { + _logger.LogError(e, e.Message); + } } private async Task AddLogItemAndSend( @@ -199,13 +251,14 @@ public sealed class InfoBasesUpdater : IDisposable InfoBaseId = infoBase.Id, TaskId = task.Id, }); + await SendLog(log, cancellationToken); } private async Task SendLog(List log, CancellationToken cancellationToken) => await _server.Send(MessageType.UpdateInfoBaseTaskLog, log, cancellationToken); - private string GetExternalDataProcessorPath(string fileName) + private static string GetExternalDataProcessorPath(string fileName) => Path.Join(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Asserts", fileName); private void Dispose(bool disposing) @@ -222,14 +275,11 @@ public sealed class InfoBasesUpdater : IDisposable _disposed = true; } - public void Dispose() + public override Task StopAsync(CancellationToken cancellationToken) { - Dispose(true); - GC.SuppressFinalize(this); - } - - ~InfoBasesUpdater() - { - Dispose(false); + _scope.Dispose(); + _server.Dispose(); + + return base.StopAsync(cancellationToken); } } \ No newline at end of file diff --git a/onecmonitor-agent/Services/InfoBases/RasHolder.cs b/onecmonitor-agent/Services/InfoBases/RasHolder.cs index 3281d36..a1a841b 100644 --- a/onecmonitor-agent/Services/InfoBases/RasHolder.cs +++ b/onecmonitor-agent/Services/InfoBases/RasHolder.cs @@ -3,6 +3,7 @@ using System.Net; using System.Net.Sockets; using OneSTools.Common.Extensions; using OneSTools.Common.Platform; +using OneSTools.Common.Platform.Services; namespace OnecMonitor.Agent.Services.InfoBases; @@ -63,7 +64,7 @@ public class RasHolder : IDisposable _processes.Remove(process); }; - _processes.Add(process!); + _processes.Add(process); var serviceModel = new RasService { diff --git a/onecmonitor-agent/Services/OnecMonitorConnection.cs b/onecmonitor-agent/Services/OnecMonitorConnection.cs index b1d32df..6b4899a 100644 --- a/onecmonitor-agent/Services/OnecMonitorConnection.cs +++ b/onecmonitor-agent/Services/OnecMonitorConnection.cs @@ -9,7 +9,8 @@ namespace OnecMonitor.Agent.Services { public class OnecMonitorConnection : ServerConnection { - public OnecMonitorConnection(IServiceProvider serviceProvider, IHostApplicationLifetime hostApplicationLifetime) + public OnecMonitorConnection(IServiceProvider serviceProvider, IHostApplicationLifetime hostApplicationLifetime) + : base(serviceProvider.GetRequiredService>()) { var configuration = serviceProvider.GetRequiredService(); diff --git a/onecmonitor-agent/appsettings.json b/onecmonitor-agent/appsettings.json index 014304c..802a1bd 100644 --- a/onecmonitor-agent/appsettings.json +++ b/onecmonitor-agent/appsettings.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Information", + "Default": "Trace", "Microsoft.Hosting.Lifetime": "Information" } }, diff --git a/onecmonitor-agent/onecmonitor-agent.csproj b/onecmonitor-agent/onecmonitor-agent.csproj index 6036f8e..6394c15 100644 --- a/onecmonitor-agent/onecmonitor-agent.csproj +++ b/onecmonitor-agent/onecmonitor-agent.csproj @@ -33,6 +33,7 @@ + diff --git a/onecmonitor-common/DTO/UpdateInfoBaseTaskDto.cs b/onecmonitor-common/DTO/UpdateInfoBaseTaskDto.cs index 5e44d94..5bb962f 100644 --- a/onecmonitor-common/DTO/UpdateInfoBaseTaskDto.cs +++ b/onecmonitor-common/DTO/UpdateInfoBaseTaskDto.cs @@ -9,6 +9,6 @@ public class UpdateInfoBaseTaskDto public Guid Id { get; set; } [Key(1)] public List InfoBases { get; set; } = []; - [Key(4)] - public List Configurations { get; set; } = []; + [Key(2)] + public List Files { get; set; } = []; } \ No newline at end of file diff --git a/onecmonitor-common/DTO/ConfigurationDto.cs b/onecmonitor-common/DTO/V8FileDto.cs similarity index 94% rename from onecmonitor-common/DTO/ConfigurationDto.cs rename to onecmonitor-common/DTO/V8FileDto.cs index 2243315..81f9f57 100644 --- a/onecmonitor-common/DTO/ConfigurationDto.cs +++ b/onecmonitor-common/DTO/V8FileDto.cs @@ -3,7 +3,7 @@ using MessagePack; namespace OnecMonitor.Common.DTO; [MessagePackObject] -public class ConfigurationDto +public class V8FileDto { [Key(0)] public Guid Id { get; set; } diff --git a/onecmonitor-common/FastConnection.cs b/onecmonitor-common/FastConnection.cs index ca391b9..3421123 100644 --- a/onecmonitor-common/FastConnection.cs +++ b/onecmonitor-common/FastConnection.cs @@ -6,13 +6,15 @@ using System.Diagnostics.SymbolStore; using System.Net.Sockets; using System.Reflection.PortableExecutable; using System.Threading.Channels; +using Microsoft.Extensions.Logging; namespace OnecMonitor.Common; -public abstract class FastConnection : IDisposable +public abstract class FastConnection(ILogger logger) : IDisposable { protected Socket? Socket; - + private CancellationToken _cancellationToken; + private readonly ConcurrentDictionary> _calls = new(); private readonly Channel _inputChannel = Channel.CreateBounded(1000); private readonly Channel _outputChannel = Channel.CreateBounded(1000); @@ -33,8 +35,10 @@ public abstract class FastConnection : IDisposable protected void RunStreamLoops(CancellationToken cancellationToken) { - _ = StartWritingToStream(cancellationToken); - _ = StartReadingFromStream(cancellationToken); + _cancellationToken = cancellationToken; + + Task.Factory.StartNew(StartWritingToStream, TaskCreationOptions.LongRunning); + Task.Factory.StartNew(StartReadingFromStream, TaskCreationOptions.LongRunning); _disconnectingEventSemaphore.Release(); } @@ -113,11 +117,14 @@ public abstract class FastConnection : IDisposable { var cts = new TaskCompletionSource(); _calls.TryAdd(message.Header.CallId, cts); - + + logger.LogTrace($"Постановка сообщения в очередь отправки. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}"); await _outputChannel.Writer.WriteAsync(message, cancellationToken); - + + logger.LogTrace($"Ожидание подтверждения на сообщение. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}"); var result = await cts.Task.WaitAsync(cancellationToken); - + + logger.LogTrace($"Подтверждение сообщения получено. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}"); if (result == null) throw new TimeoutException("Ошибка получения ответа на вызов"); @@ -139,11 +146,14 @@ public abstract class FastConnection : IDisposable { var cts = new TaskCompletionSource(); _calls.TryAdd(message.Header.CallId, cts); - + + logger.LogTrace($"Постановка сообщения в очередь отправки. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}"); await _outputChannel.Writer.WriteAsync(message, cancellationToken); - + + logger.LogTrace($"Ожидание подтверждения на сообщение. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}"); var result = await cts.Task.WaitAsync(cancellationToken); - + + logger.LogTrace($"Подтверждение сообщения получено. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}"); if (result == null) throw new TimeoutException("Ошибка получения ответа на вызов"); @@ -167,6 +177,8 @@ public abstract class FastConnection : IDisposable if (header.Length > 0) await Socket!.SendAsync(data, cancellationToken); + + logger.LogTrace($"Отправлено сообщение в поток. Тип: {header.Type}. Идентификатор: {header.CallId}"); } catch { @@ -174,18 +186,20 @@ public abstract class FastConnection : IDisposable } } - private async Task StartWritingToStream(CancellationToken cancellationToken) + private async Task StartWritingToStream() { try { - while (!cancellationToken.IsCancellationRequested) + while (!_cancellationToken.IsCancellationRequested) { - var item = await _outputChannel.Reader.ReadAsync(cancellationToken); + var message = await _outputChannel.Reader.ReadAsync(_cancellationToken); - await Socket!.SendAsync(item.Header.AsMemory(), cancellationToken); + await Socket!.SendAsync(message.Header.AsMemory(), _cancellationToken); - if (item.Data.Length > 0) - await Socket!.SendAsync(item.Data, cancellationToken); + if (message.Data.Length > 0) + await Socket!.SendAsync(message.Data, _cancellationToken); + + logger.LogTrace($"Отправлено сообщение в поток. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}"); } } catch @@ -194,29 +208,31 @@ public abstract class FastConnection : IDisposable } } - private async Task StartReadingFromStream(CancellationToken cancellationToken) + private async Task StartReadingFromStream() { try { - while (!cancellationToken.IsCancellationRequested) + while (!_cancellationToken.IsCancellationRequested) { - var headerBuffer = await ReadBytesFromStream(MessageHeader.HeaderLength, cancellationToken); + var headerBuffer = await ReadBytesFromStream(MessageHeader.HeaderLength, _cancellationToken); var header = MessageHeader.FromSpan(headerBuffer.Span); Message message; if (header.Length > 0) { - var dataBuffer = await ReadBytesFromStream(header.Length, cancellationToken); + var dataBuffer = await ReadBytesFromStream(header.Length, _cancellationToken); message = new Message(header, dataBuffer); } else message = new Message(header); + + logger.LogTrace($"Получено сообщение из потока. Тип: {header.Type}. Идентификатор: {header.CallId}"); if (_calls.TryGetValue(message.Header.CallId, out var cts)) cts.TrySetResult(message); else - await _inputChannel.Writer.WriteAsync(message, cancellationToken); + await _inputChannel.Writer.WriteAsync(message, _cancellationToken); } } catch diff --git a/onecmonitor-common/Protos/onecmonitor.proto b/onecmonitor-common/Protos/onecmonitor.proto new file mode 100644 index 0000000..d75592e --- /dev/null +++ b/onecmonitor-common/Protos/onecmonitor.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +option csharp_namespace = "OnecMonitor.Common"; + +service OnecMonitorService { + +} + +message AgentInstanceDto { + string id = 1; + string instanceName = 2; + double UtcOffset = 3; +} + +message ClusterDto { + string id = 1; + string host = 2; + int32 port = 3; +} + +message CredentialsDto { + string user = 1; + string password = 2; +} + +message InfoBaseDto { + string id = 1; + string internalId = 2; + string infoBaseName = 3; + CredentialsDto credentials = 4; + string publishAddress = 5; + ClusterDto cluster = 6; +} + diff --git a/onecmonitor-common/ServerConnection.cs b/onecmonitor-common/ServerConnection.cs index cb5ff3e..dcfcf5a 100644 --- a/onecmonitor-common/ServerConnection.cs +++ b/onecmonitor-common/ServerConnection.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.Logging; namespace OnecMonitor.Common; -public class ServerConnection : FastConnection +public class ServerConnection(ILogger logger) : FastConnection(logger) { private ILogger _logger = null!; private string _host = null!; diff --git a/onecmonitor-server/AppDbContext.cs b/onecmonitor-server/AppDbContext.cs index 9ccfa8d..16fedc6 100644 --- a/onecmonitor-server/AppDbContext.cs +++ b/onecmonitor-server/AppDbContext.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using OnecMonitor.Server.Converters.Sqlite; using OnecMonitor.Server.Models; using OnecMonitor.Common.DTO; +using OnecMonitor.Server.Models.MaintenanceTasks; namespace OnecMonitor.Server { @@ -15,13 +16,15 @@ namespace OnecMonitor.Server public DbSet LogTemplates { get; set; } public DbSet TechLogSeances { get; set; } public DbSet TechLogFilters { get; set; } - public DbSet Configurations { get; set; } + public DbSet V8Files { get; set; } public DbSet Credentials { get; set; } public DbSet InfoBases { get; set; } public DbSet Clusters { get; set; } public DbSet UpdateInfoBaseTasks { get; set; } public DbSet UpdateInfoBaseTaskLogItems { get; set; } public DbSet TechLogSettings { get; set; } + public DbSet MaintenanceTasks { get; set; } + public DbSet MaintenanceStepNodes { get; set; } public AppDbContext(IHostEnvironment hostEnvironment) => DbPath = Path.Join(hostEnvironment.ContentRootPath, "om-server.db"); diff --git a/onecmonitor-server/AutoMapper/CommonProfile.cs b/onecmonitor-server/AutoMapper/CommonProfile.cs index 0e2bbde..e317580 100644 --- a/onecmonitor-server/AutoMapper/CommonProfile.cs +++ b/onecmonitor-server/AutoMapper/CommonProfile.cs @@ -1,10 +1,14 @@ +using System.Text.Json; +using System.Text.Json.Serialization; using AutoMapper; using OnecMonitor.Server.Models; +using OnecMonitor.Server.Models.MaintenanceTasks; using OnecMonitor.Server.ViewModels.Agents; using OnecMonitor.Server.ViewModels.Clusters; -using OnecMonitor.Server.ViewModels.Configurations; +using OnecMonitor.Server.ViewModels.V8Files; using OnecMonitor.Server.ViewModels.Credentials; using OnecMonitor.Server.ViewModels.InfoBases; +using OnecMonitor.Server.ViewModels.MaintenanceTasks; using OnecMonitor.Server.ViewModels.TechLogSeances; using OnecMonitor.Server.ViewModels.TechLogSettings; using OnecMonitor.Server.ViewModels.UpdateInfoBaseTasks; @@ -22,7 +26,7 @@ public class CommonProfile : Profile CreateMap().ReverseMap(); CreateMap().ReverseMap(); CreateMap().ReverseMap(); - CreateMap() + CreateMap() .ForMember(c => c.Name, opt => opt.MapFrom(src => src.ToString())) .ReverseMap(); @@ -36,11 +40,11 @@ public class CommonProfile : Profile .ForMember(c => c.InfoBases, i => i.Ignore()) .ForMember(c => c.Clusters, i => i.Ignore()); - CreateMap() + CreateMap() .ReverseMap() .ForMember(c => c.Id, i => i.Ignore()); - CreateMap() + CreateMap() .ReverseMap() .ForMember(c => c.Id, i => i.Ignore()); @@ -67,7 +71,7 @@ public class CommonProfile : Profile .ForMember(c => c.Id, i => i.Ignore()) .ForMember(c => c.Cluster, i => i.Ignore()); - CreateMap() + CreateMap() .ReverseMap() .ForMember(c => c.Id, i => i.Ignore()); @@ -79,12 +83,32 @@ public class CommonProfile : Profile CreateMap() .ReverseMap() .ForMember(c => c.Id, i => i.Ignore()) - .ForMember(c => c.Configurations, i => i.Ignore()) + .ForMember(c => c.Files, i => i.Ignore()) .ForMember(c => c.InfoBases, i => i.Ignore()) .ForMember(c => c.Log, i => i.Ignore()); CreateMap() .ReverseMap() .ForMember(c => c.Id, i => i.Ignore()); + + CreateMap() + .ReverseMap() + .ForMember(c => c.Id, i => i.Ignore()) + .ForMember(c => c.InfoBases, i => i.Ignore()); + + CreateMap().ReverseMap(); + + CreateMap() + .ReverseMap() + .ForMember(c => c.Id, i => i.Ignore()) + .ForMember(c => c.File, i => i.Ignore()); + + CreateMap() + .ForMember(c => c.StepId, i => i.MapFrom(src => src.Step.Id)) + .ReverseMap() + .ForMember(c => c.Id, i => i.Ignore()) + .ForMember(c => c.LeftNode, i => i.Ignore()) + .ForMember(c => c.RightNode, i => i.Ignore()) + .ForMember(c => c.Step, i => i.Ignore()); } } \ No newline at end of file diff --git a/onecmonitor-server/AutoMapper/DtoProfile.cs b/onecmonitor-server/AutoMapper/DtoProfile.cs index 753c933..3bbacf8 100644 --- a/onecmonitor-server/AutoMapper/DtoProfile.cs +++ b/onecmonitor-server/AutoMapper/DtoProfile.cs @@ -14,7 +14,7 @@ public class DtoProfile : Profile .ForMember(c => c.Id, opt => opt.MapFrom(src => src.ClusterInternalId)) .ReverseMap(); - CreateMap() + CreateMap() .ForMember(c => c.Data, opt => opt.MapFrom(src => File.ReadAllBytes(src.DataPath))) .ReverseMap(); diff --git a/onecmonitor-server/Controllers/CredentialsController.cs b/onecmonitor-server/Controllers/CredentialsController.cs index 7f77b28..b252c14 100644 --- a/onecmonitor-server/Controllers/CredentialsController.cs +++ b/onecmonitor-server/Controllers/CredentialsController.cs @@ -38,7 +38,7 @@ public class CredentialsController(AppDbContext appDbContext, IMapper mapper) : if (!ModelState.IsValid) return View("Edit", await PrepareViewModel(vm, cancellationToken)); - var model = isNew ? new Credentials() + var model = isNew ? new Credentials { Id = Guid.NewGuid() } : await appDbContext.Credentials diff --git a/onecmonitor-server/Controllers/MaintenanceController.cs b/onecmonitor-server/Controllers/MaintenanceController.cs deleted file mode 100644 index 3dbefad..0000000 --- a/onecmonitor-server/Controllers/MaintenanceController.cs +++ /dev/null @@ -1,73 +0,0 @@ -using AutoMapper; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using OnecMonitor.Server.Helpers; -using OnecMonitor.Server.Models; -using OnecMonitor.Server.ViewModels.Maintenance; - -namespace OnecMonitor.Server.Controllers; - -public class MaintenanceController(AppDbContext appDbContext, IMapper mapper) : Controller -{ - public async Task Index() - { - return View(new MaintenanceIndexViewModel()); - } - - public async Task Edit(Guid id, CancellationToken cancellationToken) - { - return View(await PrepareViewModel(new MaintenanceEditViewModel(), cancellationToken)); - } - - public IActionResult Step(MaintenanceStepViewModel vm) - => vm.Kind switch - { - MaintenanceStepKind.LockConnections - or MaintenanceStepKind.LoadExtension - or MaintenanceStepKind.UpdateConfiguration - or MaintenanceStepKind.LoadConfiguration - or MaintenanceStepKind.StartExternalDataProcessor => PartialView($"Steps/{vm.Kind}", vm), - MaintenanceStepKind.UnlockConnections or MaintenanceStepKind.CloseConnections - or MaintenanceStepKind.UpdateDatabase => Ok(), - _ => NotFound() - }; - - public IActionResult ValidateStep(MaintenanceStepViewModel vm) - { - // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault - switch (vm.Kind) - { - case MaintenanceStepKind.LoadExtension: - case MaintenanceStepKind.UpdateConfiguration: - case MaintenanceStepKind.LoadConfiguration: - case MaintenanceStepKind.StartExternalDataProcessor: - if (vm.FileId == null || vm.FileId == Guid.Empty) - ModelState.AddModelError(nameof(vm.FileId), "Не указан файл"); - break; - case MaintenanceStepKind.LockConnections: - if (string.IsNullOrEmpty(vm.AccessCode)) - ModelState.AddModelError(nameof(vm.AccessCode), "Не указан код доступа"); - if (string.IsNullOrEmpty(vm.Message)) - ModelState.AddModelError(nameof(vm.Message), "Не указан тест сообщения"); - break; - } - - var view = PartialView($"Steps/{vm.Kind}", vm); - view.StatusCode = ModelState.IsValid ? 200 : 400; - - return view; - } - - private async Task PrepareViewModel(MaintenanceEditViewModel vm, CancellationToken cancellationToken) - { - vm.AvailableInfoBases = await UiHelper.SelectableItemsFrom( - appDbContext.InfoBases, - vm.InfoBases, - mapper, - cancellationToken); - - vm.MaintenanceStepKinds = UiHelper.SelectListFromEnum(); - - return vm; - } -} \ No newline at end of file diff --git a/onecmonitor-server/Controllers/MaintenanceTasksController.cs b/onecmonitor-server/Controllers/MaintenanceTasksController.cs new file mode 100644 index 0000000..ab0ff13 --- /dev/null +++ b/onecmonitor-server/Controllers/MaintenanceTasksController.cs @@ -0,0 +1,182 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using OnecMonitor.Server.Helpers; +using OnecMonitor.Server.Models; +using OnecMonitor.Server.Models.MaintenanceTasks; +using OnecMonitor.Server.ViewModels; +using OnecMonitor.Server.ViewModels.MaintenanceTasks; + +namespace OnecMonitor.Server.Controllers; + +public class MaintenanceTasksController(AppDbContext appDbContext, IMapper mapper) : Controller +{ + private readonly JsonSerializerOptions _jsonOptions = new() + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + + public async Task Index(CancellationToken cancellationToken) + => View(new MaintenanceTasksIndexViewModel + { + Items = await appDbContext.MaintenanceTasks + .AsNoTracking() + .ProjectTo(mapper.ConfigurationProvider) + .ToListAsync(cancellationToken) + }); + + public async Task Edit(Guid id, CancellationToken cancellationToken) + { + if (id == Guid.Empty) + return View(await PrepareViewModel(new MaintenanceTaskEditViewModel(), cancellationToken)); + + var model = await appDbContext.MaintenanceTasks + .AsNoTracking() + .Include(c => c.RootNode) + .ThenInclude(c => c.Step) + .AsNoTracking() + .Include(c => c.InfoBases) + .FirstOrDefaultAsync(c => c.Id == id, cancellationToken); + + if (model == null) + return NotFound(); + + await LoadNodesRecursively(model.RootNode, cancellationToken); + + var vm = mapper.Map(model); + var rootNodeVm = mapper.Map(model.RootNode); + vm.SerializedStepNode = JsonSerializer.Serialize(rootNodeVm, _jsonOptions); + + return View(await PrepareViewModel(vm, cancellationToken)); + } + + [HttpPost] + public async Task Save(MaintenanceTaskEditViewModel vm, CancellationToken cancellationToken) + { + var isNew = vm.Id == Guid.Empty; + + if (!ModelState.IsValid) + return View("Edit", await PrepareViewModel(vm, cancellationToken)); + + var model = isNew ? new MaintenanceTask + { + Id = Guid.NewGuid() + } : await appDbContext.MaintenanceTasks + .Include(c => c.RootNode) + .FirstOrDefaultAsync(i => i.Id == vm.Id, cancellationToken); + + if (model == null) + return NotFound(); + + mapper.Map(vm, model); + model.RootNode = JsonSerializer.Deserialize(vm.SerializedStepNode, _jsonOptions)!; + model.RootNodeId = model.RootNode.Id; + + await UiHelper.UpdateModelItems(appDbContext.InfoBases, vm.InfoBases, model.InfoBases, cancellationToken); + + if (isNew) + appDbContext.MaintenanceTasks.Add(model); + else + appDbContext.MaintenanceTasks.Update(model); + + await appDbContext.SaveChangesAsync(cancellationToken); + + return RedirectToAction("Index"); + } + + [HttpPost] + public async Task EditStep(CancellationToken cancellationToken) + { + var vm = await HttpContext.Request.ReadFromJsonAsync(_jsonOptions, cancellationToken); + return await UpdateStep(vm!, cancellationToken); + } + + [HttpPost] + public async Task UpdateStep(MaintenanceStepViewModel vm, CancellationToken cancellationToken) + => PartialView("EditStep", await PrepareViewModel(vm, cancellationToken)); + + [HttpPost] + public async Task ValidateStep(MaintenanceStepViewModel vm, CancellationToken cancellationToken) + { + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (vm.Kind) + { + case MaintenanceStepKind.LoadExtension: + case MaintenanceStepKind.UpdateConfiguration: + case MaintenanceStepKind.LoadConfiguration: + case MaintenanceStepKind.StartExternalDataProcessor: + if (vm.FileId == null || vm.FileId == Guid.Empty) + ModelState.AddModelError(nameof(vm.FileId), "Не указан файл"); + break; + case MaintenanceStepKind.LockConnections: + if (string.IsNullOrEmpty(vm.AccessCode)) + ModelState.AddModelError(nameof(vm.AccessCode), "Не указан код доступа"); + if (string.IsNullOrEmpty(vm.Message)) + ModelState.AddModelError(nameof(vm.Message), "Не указан тест сообщения"); + break; + } + + if (ModelState.IsValid) + return Json(vm); + + var view = PartialView("EditStep", await PrepareViewModel(vm, cancellationToken)); + view.StatusCode = 400; + + return view; + } + + private async Task PrepareViewModel(MaintenanceTaskEditViewModel vm, CancellationToken cancellationToken) + { + vm.AvailableInfoBases = await UiHelper.SelectableItemsFrom( + appDbContext.InfoBases, + vm.InfoBases, + mapper, + cancellationToken); + + return vm; + } + + private async Task PrepareViewModel(MaintenanceStepViewModel vm, CancellationToken cancellationToken) + { + vm.Kinds = UiHelper.SelectListFromEnum(vm.Kind); + + var files = vm.Kind switch + { + MaintenanceStepKind.LoadConfiguration => appDbContext.V8Files.Where(c => c.IsConfiguration), + MaintenanceStepKind.LoadExtension => appDbContext.V8Files.Where(c => c.IsExtension), + MaintenanceStepKind.UpdateConfiguration => appDbContext.V8Files.Where(c => c.IsUpdate), + MaintenanceStepKind.StartExternalDataProcessor => appDbContext.V8Files.Where(c => c.IsExternalDataProcessor), + _ => null + }; + + if (files is not null) + vm.Files = await UiHelper.SelectListFrom(files, c => c.ToString(), vm.FileId, cancellationToken); + + return vm; + } + + private async Task LoadNodesRecursively(MaintenanceStepNode node, CancellationToken cancellationToken) + { + if (node.LeftNodeId != null && node.LeftNodeId != Guid.Empty) + node.LeftNode = await LoadNodeRecursively(node.LeftNodeId, cancellationToken); + + if (node.RightNodeId != null && node.RightNodeId != Guid.Empty) + node.RightNode = await LoadNodeRecursively(node.RightNodeId, cancellationToken); + } + + private async Task LoadNodeRecursively(Guid? id, CancellationToken cancellationToken) + { + var node = (await appDbContext.MaintenanceStepNodes + .AsNoTracking() + .Include(c => c.Step) + .FirstOrDefaultAsync(c => c.Id == id, cancellationToken))!; + + await LoadNodesRecursively(node, cancellationToken); + + return node; + } +} \ No newline at end of file diff --git a/onecmonitor-server/Controllers/UpdateInfoBaseTasksController.cs b/onecmonitor-server/Controllers/UpdateInfoBaseTasksController.cs index bdba9d8..ce4bb32 100644 --- a/onecmonitor-server/Controllers/UpdateInfoBaseTasksController.cs +++ b/onecmonitor-server/Controllers/UpdateInfoBaseTasksController.cs @@ -27,7 +27,7 @@ public class UpdateInfoBaseTasksController(AppDbContext appDbContext, AgentsConn var vm = id != Guid.Empty ? await appDbContext.UpdateInfoBaseTasks .AsNoTracking() - .Include(c => c.Configurations) + .Include(c => c.Files) .Include(c => c.InfoBases) .ProjectTo(mapper.ConfigurationProvider) .FirstOrDefaultAsync(cancellationToken) @@ -43,8 +43,8 @@ public class UpdateInfoBaseTasksController(AppDbContext appDbContext, AgentsConn { var isNew = vm.Id == Guid.Empty; - var configIds = vm.Configurations.Select(c => c.Id).ToList(); - var configs = appDbContext.Configurations + var configIds = vm.Files.Select(c => c.Id).ToList(); + var configs = appDbContext.V8Files .AsNoTracking() .Where(c => configIds.Contains(c.Id.ToString())) .ToList(); @@ -52,7 +52,7 @@ public class UpdateInfoBaseTasksController(AppDbContext appDbContext, AgentsConn var countOfUpdatesAndConfigs = configs.Count(c => c.IsUpdate || c.IsConfiguration); if (countOfUpdatesAndConfigs > 1) ModelState.AddModelError( - nameof(UpdateInfoBaseTaskEditViewModel.Configurations), + nameof(UpdateInfoBaseTaskEditViewModel.Files), "Список конфигураций может содержать только одну конфигурацию или обновление конфигурации"); if (!ModelState.IsValid) @@ -63,7 +63,7 @@ public class UpdateInfoBaseTasksController(AppDbContext appDbContext, AgentsConn Id = Guid.NewGuid() } : await appDbContext.UpdateInfoBaseTasks .Include(c => c.InfoBases) - .Include(c => c.Configurations) + .Include(c => c.Files) .Include(c => c.Log) .FirstOrDefaultAsync(i => i.Id == vm.Id, cancellationToken); @@ -76,7 +76,7 @@ public class UpdateInfoBaseTasksController(AppDbContext appDbContext, AgentsConn mapper.Map(vm, model); await UiHelper.UpdateModelItems(appDbContext.InfoBases, vm.InfoBases, model.InfoBases, cancellationToken); - await UiHelper.UpdateModelItems(appDbContext.Configurations, vm.Configurations, model.Configurations, cancellationToken); + await UiHelper.UpdateModelItems(appDbContext.V8Files, vm.Files, model.Files, cancellationToken); await appDbContext.SaveChangesAsync(cancellationToken); @@ -190,9 +190,9 @@ public class UpdateInfoBaseTasksController(AppDbContext appDbContext, AgentsConn private async Task PrepareViewModel(UpdateInfoBaseTaskEditViewModel vm, CancellationToken cancellationToken) { - vm.AvailableConfigurations = await UiHelper.SelectableItemsFrom( - appDbContext.Configurations, - vm.Configurations, + vm.AvailableFiles = await UiHelper.SelectableItemsFrom( + appDbContext.V8Files, + vm.Files, mapper, cancellationToken); diff --git a/onecmonitor-server/Controllers/ConfigurationsController.cs b/onecmonitor-server/Controllers/V8FilesController.cs similarity index 65% rename from onecmonitor-server/Controllers/ConfigurationsController.cs rename to onecmonitor-server/Controllers/V8FilesController.cs index 275fa81..7fcc899 100644 --- a/onecmonitor-server/Controllers/ConfigurationsController.cs +++ b/onecmonitor-server/Controllers/V8FilesController.cs @@ -1,26 +1,29 @@ +using System.Net.Http.Headers; using System.Runtime.InteropServices.JavaScript; using System.Text.Json; using AutoMapper; using AutoMapper.QueryableExtensions; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.WebUtilities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using OnecMonitor.Server; using OnecMonitor.Server.Models; using OnecMonitor.Server.ViewModels; -using OnecMonitor.Server.ViewModels.Configurations; +using OnecMonitor.Server.ViewModels.V8Files; +using OneSTools.Common.Platform.Unpack; namespace OnecMonitor.Server.Controllers; -public class ConfigurationsController(AppDbContext appDbContext, IMapper mapper, IWebHostEnvironment webHostEnvironment) : Controller +public class V8FilesController(AppDbContext appDbContext, IMapper mapper, IWebHostEnvironment webHostEnvironment) : Controller { public async Task Index() { - return View(new ConfigurationsIndexViewModel + return View(new V8FilesIndexViewModel { - Items = await appDbContext.Configurations - .ProjectTo(mapper.ConfigurationProvider) + Items = await appDbContext.V8Files + .ProjectTo(mapper.ConfigurationProvider) .ToListAsync() }); } @@ -28,14 +31,14 @@ public class ConfigurationsController(AppDbContext appDbContext, IMapper mapper, public async Task Edit(Guid id) { if (id == Guid.Empty) - return View(new ConfigurationEditViewModel()); + return View(new V8FileEditViewModel()); - var item = await appDbContext.Configurations.FindAsync(id); + var item = await appDbContext.V8Files.FindAsync(id); if (item == null) return NotFound(); - return View(new ConfigurationEditViewModel + return View(new V8FileEditViewModel { Id = item.Id, Name = item.Name, @@ -48,31 +51,46 @@ public class ConfigurationsController(AppDbContext appDbContext, IMapper mapper, MultipartBodyLengthLimit = int.MaxValue, ValueLengthLimit = int.MaxValue) ] - public async Task Save(Guid id, ConfigurationEditViewModel vm, CancellationToken cancellationToken) + public async Task Save(Guid id, V8FileEditViewModel vm, CancellationToken cancellationToken) { var isNew = id == Guid.Empty; - + if (!isNew) - ModelState.Remove(nameof(ConfigurationEditViewModel.File)); + ModelState.Remove(nameof(V8FileEditViewModel.File)); if (!ModelState.IsValid) return View("Edit", vm); - var model = isNew ? new V8Configuration + var model = isNew ? new V8File { Id = Guid.NewGuid() - } : await appDbContext.Configurations.FindAsync([id], cancellationToken); + } : await appDbContext.V8Files.FindAsync([id], cancellationToken); if (model == null) return NotFound(); - + if (isNew) { // save file var extension = Path.GetExtension(vm.File.FileName); + + if (extension.Equals(".CF", StringComparison.InvariantCultureIgnoreCase)) + model.IsConfiguration = true; + else if (extension.Equals(".CFE", StringComparison.InvariantCultureIgnoreCase)) + model.IsExtension = true; + else if (extension.Equals(".CFU", StringComparison.InvariantCultureIgnoreCase)) + model.IsUpdate = true; + else if (extension.Equals(".EPF", StringComparison.InvariantCultureIgnoreCase)) + model.IsExternalDataProcessor = true; + else + ModelState.AddModelError(nameof(V8FileEditViewModel.File), "Invalid file format"); + + if (!ModelState.IsValid) + return View("Edit", vm); + var fileName = $"{vm.Name}_{vm.Version}{extension}"; var path = Path.Combine(webHostEnvironment.ContentRootPath, "Data", fileName); - + if (System.IO.File.Exists(path)) return View("Error", new ErrorViewModel($"File {path} already exists.")); @@ -81,34 +99,27 @@ public class ConfigurationsController(AppDbContext appDbContext, IMapper mapper, model.DataPath = path; - await appDbContext.Configurations.AddAsync(model, cancellationToken); - - if (extension.Equals(".CF", StringComparison.InvariantCultureIgnoreCase)) - model.IsConfiguration = true; - else if (extension.Equals(".CFE", StringComparison.InvariantCultureIgnoreCase)) - model.IsExtension = true; - else if (extension.Equals(".CFU", StringComparison.InvariantCultureIgnoreCase)) - model.IsUpdate = true; + await appDbContext.V8Files.AddAsync(model, cancellationToken); } - + model.Name = vm.Name; model.Version = vm.Version; - + await appDbContext.SaveChangesAsync(cancellationToken); - + return RedirectToAction("Index"); } public async Task Delete(Guid id, CancellationToken cancellationToken) { - var item = await appDbContext.Configurations.FindAsync( + var item = await appDbContext.V8Files.FindAsync( [id], cancellationToken: cancellationToken); if (System.IO.File.Exists(item!.DataPath)) System.IO.File.Delete(item.DataPath); - appDbContext.Configurations.Remove(item!); + appDbContext.V8Files.Remove(item!); await appDbContext.SaveChangesAsync(cancellationToken); return RedirectToAction("Index"); diff --git a/onecmonitor-server/Extensions/EnumExtension.cs b/onecmonitor-server/Extensions/EnumExtension.cs new file mode 100644 index 0000000..96bcafc --- /dev/null +++ b/onecmonitor-server/Extensions/EnumExtension.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; + +namespace OnecMonitor.Server.Extensions; + +public static class EnumExtension +{ + public static string GetDisplay(this Enum value) + => value.GetAttributeOfType()?.Name ?? value.ToString(); + + private static T? GetAttributeOfType(this Enum enumVal) where T : Attribute + { + var type = enumVal.GetType(); + var memInfo = type.GetMember(enumVal.ToString()); + var attributes = memInfo[0].GetCustomAttributes(typeof(T), false); + return (attributes.Length > 0) ? (T)attributes[0] : null; + } +} \ No newline at end of file diff --git a/onecmonitor-server/Helpers/UiHelper.cs b/onecmonitor-server/Helpers/UiHelper.cs index a42578b..a90a00d 100644 --- a/onecmonitor-server/Helpers/UiHelper.cs +++ b/onecmonitor-server/Helpers/UiHelper.cs @@ -9,7 +9,7 @@ namespace OnecMonitor.Server.Helpers; public static class UiHelper { - public static SelectList SelectListFromEnum() where T1 : struct, Enum + public static SelectList SelectListFromEnum(T1? selectedValue = null) where T1 : struct, Enum { var values = Enum.GetValues().ToList(); var selectListItems = values.Select(i => new { Id = i.ToString(), Name = i.GetAttributeOfType()?.Name ?? i.ToString() }).ToList(); @@ -18,7 +18,8 @@ public static class UiHelper return new SelectList( selectListItems, "Id", - "Name"); + "Name", + selectedValue == null ? "" : selectedValue); } public static async Task SelectListFrom( diff --git a/onecmonitor-server/Migrations/20250214151907_Initial.Designer.cs b/onecmonitor-server/Migrations/20250225183414_Initial.Designer.cs similarity index 75% rename from onecmonitor-server/Migrations/20250214151907_Initial.Designer.cs rename to onecmonitor-server/Migrations/20250225183414_Initial.Designer.cs index 3e36bc4..7e38146 100644 --- a/onecmonitor-server/Migrations/20250214151907_Initial.Designer.cs +++ b/onecmonitor-server/Migrations/20250225183414_Initial.Designer.cs @@ -10,7 +10,7 @@ using OnecMonitor.Server; namespace OnecMonitor.Server.Migrations { [DbContext(typeof(AppDbContext))] - [Migration("20250214151907_Initial")] + [Migration("20250225183414_Initial")] partial class Initial { /// @@ -170,6 +170,9 @@ namespace OnecMonitor.Server.Migrations .IsRequired() .HasColumnType("TEXT"); + b.Property("MaintenanceTaskId") + .HasColumnType("TEXT"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT"); @@ -184,6 +187,8 @@ namespace OnecMonitor.Server.Migrations b.HasIndex("CredentialsId"); + b.HasIndex("MaintenanceTaskId"); + b.ToTable("InfoBases"); }); @@ -206,6 +211,87 @@ namespace OnecMonitor.Server.Migrations b.ToTable("LogTemplates"); }); + modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStep", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("FileId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FileId"); + + b.ToTable("MaintenanceStep"); + }); + + modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("LeftNodeId") + .HasColumnType("TEXT"); + + b.Property("RightNodeId") + .HasColumnType("TEXT"); + + b.Property("StepId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LeftNodeId"); + + b.HasIndex("RightNodeId"); + + b.HasIndex("StepId"); + + b.ToTable("MaintenanceStepNodes"); + }); + + modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RootNodeId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RootNodeId"); + + b.ToTable("MaintenanceTasks"); + }); + modelBuilder.Entity("OnecMonitor.Server.Models.TechLogFilter", b => { b.Property("Id") @@ -344,7 +430,7 @@ namespace OnecMonitor.Server.Migrations b.ToTable("UpdateInfoBaseTaskLogItems"); }); - modelBuilder.Entity("OnecMonitor.Server.Models.V8Configuration", b => + modelBuilder.Entity("OnecMonitor.Server.Models.V8File", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -360,6 +446,9 @@ namespace OnecMonitor.Server.Migrations b.Property("IsExtension") .HasColumnType("INTEGER"); + b.Property("IsExternalDataProcessor") + .HasColumnType("INTEGER"); + b.Property("IsUpdate") .HasColumnType("INTEGER"); @@ -373,22 +462,22 @@ namespace OnecMonitor.Server.Migrations b.HasKey("Id"); - b.ToTable("Configurations"); + b.ToTable("V8Files"); }); - modelBuilder.Entity("UpdateInfoBaseTaskV8Configuration", b => + modelBuilder.Entity("UpdateInfoBaseTaskV8File", b => { - b.Property("ConfigurationsId") + b.Property("FilesId") .HasColumnType("TEXT"); b.Property("UpdateTasksId") .HasColumnType("TEXT"); - b.HasKey("ConfigurationsId", "UpdateTasksId"); + b.HasKey("FilesId", "UpdateTasksId"); b.HasIndex("UpdateTasksId"); - b.ToTable("UpdateInfoBaseTaskV8Configuration"); + b.ToTable("UpdateInfoBaseTaskV8File"); }); modelBuilder.Entity("AgentTechLogSeance", b => @@ -467,11 +556,58 @@ namespace OnecMonitor.Server.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceTask", null) + .WithMany("InfoBases") + .HasForeignKey("MaintenanceTaskId"); + b.Navigation("Cluster"); b.Navigation("Credentials"); }); + modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStep", b => + { + b.HasOne("OnecMonitor.Server.Models.V8File", "File") + .WithMany() + .HasForeignKey("FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", b => + { + b.HasOne("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", "LeftNode") + .WithMany() + .HasForeignKey("LeftNodeId"); + + b.HasOne("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", "RightNode") + .WithMany() + .HasForeignKey("RightNodeId"); + + b.HasOne("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStep", "Step") + .WithMany() + .HasForeignKey("StepId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeftNode"); + + b.Navigation("RightNode"); + + b.Navigation("Step"); + }); + + modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceTask", b => + { + b.HasOne("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", "RootNode") + .WithMany() + .HasForeignKey("RootNodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RootNode"); + }); + modelBuilder.Entity("OnecMonitor.Server.Models.UpdateInfoBaseTaskLogItem", b => { b.HasOne("OnecMonitor.Server.Models.InfoBase", "InfoBase") @@ -491,11 +627,11 @@ namespace OnecMonitor.Server.Migrations b.Navigation("Task"); }); - modelBuilder.Entity("UpdateInfoBaseTaskV8Configuration", b => + modelBuilder.Entity("UpdateInfoBaseTaskV8File", b => { - b.HasOne("OnecMonitor.Server.Models.V8Configuration", null) + b.HasOne("OnecMonitor.Server.Models.V8File", null) .WithMany() - .HasForeignKey("ConfigurationsId") + .HasForeignKey("FilesId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -523,6 +659,11 @@ namespace OnecMonitor.Server.Migrations b.Navigation("InfoBases"); }); + modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceTask", b => + { + b.Navigation("InfoBases"); + }); + modelBuilder.Entity("OnecMonitor.Server.Models.UpdateInfoBaseTask", b => { b.Navigation("Log"); diff --git a/onecmonitor-server/Migrations/20250214151907_Initial.cs b/onecmonitor-server/Migrations/20250225183414_Initial.cs similarity index 73% rename from onecmonitor-server/Migrations/20250214151907_Initial.cs rename to onecmonitor-server/Migrations/20250225183414_Initial.cs index c30b51c..50bd555 100644 --- a/onecmonitor-server/Migrations/20250214151907_Initial.cs +++ b/onecmonitor-server/Migrations/20250225183414_Initial.cs @@ -22,23 +22,6 @@ namespace OnecMonitor.Server.Migrations table.PrimaryKey("PK_Agents", x => x.Id); }); - migrationBuilder.CreateTable( - name: "Configurations", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: false), - Version = table.Column(type: "TEXT", nullable: false), - DataPath = table.Column(type: "TEXT", nullable: false), - IsUpdate = table.Column(type: "INTEGER", nullable: false), - IsExtension = table.Column(type: "INTEGER", nullable: false), - IsConfiguration = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Configurations", x => x.Id); - }); - migrationBuilder.CreateTable( name: "Credentials", columns: table => new @@ -127,6 +110,24 @@ namespace OnecMonitor.Server.Migrations table.PrimaryKey("PK_UpdateInfoBaseTasks", x => x.Id); }); + migrationBuilder.CreateTable( + name: "V8Files", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Version = table.Column(type: "TEXT", nullable: false), + DataPath = table.Column(type: "TEXT", nullable: false), + IsUpdate = table.Column(type: "INTEGER", nullable: false), + IsExtension = table.Column(type: "INTEGER", nullable: false), + IsConfiguration = table.Column(type: "INTEGER", nullable: false), + IsExternalDataProcessor = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_V8Files", x => x.Id); + }); + migrationBuilder.CreateTable( name: "Clusters", columns: table => new @@ -204,25 +205,95 @@ namespace OnecMonitor.Server.Migrations }); migrationBuilder.CreateTable( - name: "UpdateInfoBaseTaskV8Configuration", + name: "MaintenanceStep", columns: table => new { - ConfigurationsId = table.Column(type: "TEXT", nullable: false), + Id = table.Column(type: "TEXT", nullable: false), + Kind = table.Column(type: "INTEGER", nullable: false), + AccessCode = table.Column(type: "TEXT", maxLength: 20, nullable: false), + Message = table.Column(type: "TEXT", maxLength: 200, nullable: false), + FileId = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MaintenanceStep", x => x.Id); + table.ForeignKey( + name: "FK_MaintenanceStep_V8Files_FileId", + column: x => x.FileId, + principalTable: "V8Files", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "UpdateInfoBaseTaskV8File", + columns: table => new + { + FilesId = table.Column(type: "TEXT", nullable: false), UpdateTasksId = table.Column(type: "TEXT", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_UpdateInfoBaseTaskV8Configuration", x => new { x.ConfigurationsId, x.UpdateTasksId }); + table.PrimaryKey("PK_UpdateInfoBaseTaskV8File", x => new { x.FilesId, x.UpdateTasksId }); table.ForeignKey( - name: "FK_UpdateInfoBaseTaskV8Configuration_Configurations_ConfigurationsId", - column: x => x.ConfigurationsId, - principalTable: "Configurations", + name: "FK_UpdateInfoBaseTaskV8File_UpdateInfoBaseTasks_UpdateTasksId", + column: x => x.UpdateTasksId, + principalTable: "UpdateInfoBaseTasks", principalColumn: "Id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_UpdateInfoBaseTaskV8Configuration_UpdateInfoBaseTasks_UpdateTasksId", - column: x => x.UpdateTasksId, - principalTable: "UpdateInfoBaseTasks", + name: "FK_UpdateInfoBaseTaskV8File_V8Files_FilesId", + column: x => x.FilesId, + principalTable: "V8Files", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MaintenanceStepNodes", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Kind = table.Column(type: "INTEGER", nullable: false), + LeftNodeId = table.Column(type: "TEXT", nullable: true), + RightNodeId = table.Column(type: "TEXT", nullable: true), + StepId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MaintenanceStepNodes", x => x.Id); + table.ForeignKey( + name: "FK_MaintenanceStepNodes_MaintenanceStepNodes_LeftNodeId", + column: x => x.LeftNodeId, + principalTable: "MaintenanceStepNodes", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_MaintenanceStepNodes_MaintenanceStepNodes_RightNodeId", + column: x => x.RightNodeId, + principalTable: "MaintenanceStepNodes", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_MaintenanceStepNodes_MaintenanceStep_StepId", + column: x => x.StepId, + principalTable: "MaintenanceStep", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MaintenanceTasks", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Description = table.Column(type: "TEXT", maxLength: 200, nullable: false), + RootNodeId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MaintenanceTasks", x => x.Id); + table.ForeignKey( + name: "FK_MaintenanceTasks_MaintenanceStepNodes_RootNodeId", + column: x => x.RootNodeId, + principalTable: "MaintenanceStepNodes", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); @@ -237,7 +308,8 @@ namespace OnecMonitor.Server.Migrations InfoBaseName = table.Column(type: "TEXT", nullable: false), PublishAddress = table.Column(type: "TEXT", nullable: false), CredentialsId = table.Column(type: "TEXT", nullable: false), - ClusterId = table.Column(type: "TEXT", nullable: false) + ClusterId = table.Column(type: "TEXT", nullable: false), + MaintenanceTaskId = table.Column(type: "TEXT", nullable: true) }, constraints: table => { @@ -254,6 +326,11 @@ namespace OnecMonitor.Server.Migrations principalTable: "Credentials", principalColumn: "Id", onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_InfoBases_MaintenanceTasks_MaintenanceTaskId", + column: x => x.MaintenanceTaskId, + principalTable: "MaintenanceTasks", + principalColumn: "Id"); }); migrationBuilder.CreateTable( @@ -334,6 +411,11 @@ namespace OnecMonitor.Server.Migrations table: "InfoBases", column: "CredentialsId"); + migrationBuilder.CreateIndex( + name: "IX_InfoBases_MaintenanceTaskId", + table: "InfoBases", + column: "MaintenanceTaskId"); + migrationBuilder.CreateIndex( name: "IX_InfoBaseUpdateInfoBaseTask_UpdateTasksId", table: "InfoBaseUpdateInfoBaseTask", @@ -344,6 +426,31 @@ namespace OnecMonitor.Server.Migrations table: "LogTemplateTechLogSeance", column: "TemplatesId"); + migrationBuilder.CreateIndex( + name: "IX_MaintenanceStep_FileId", + table: "MaintenanceStep", + column: "FileId"); + + migrationBuilder.CreateIndex( + name: "IX_MaintenanceStepNodes_LeftNodeId", + table: "MaintenanceStepNodes", + column: "LeftNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_MaintenanceStepNodes_RightNodeId", + table: "MaintenanceStepNodes", + column: "RightNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_MaintenanceStepNodes_StepId", + table: "MaintenanceStepNodes", + column: "StepId"); + + migrationBuilder.CreateIndex( + name: "IX_MaintenanceTasks_RootNodeId", + table: "MaintenanceTasks", + column: "RootNodeId"); + migrationBuilder.CreateIndex( name: "IX_UpdateInfoBaseTaskLogItems_InfoBaseId", table: "UpdateInfoBaseTaskLogItems", @@ -355,8 +462,8 @@ namespace OnecMonitor.Server.Migrations column: "TaskId"); migrationBuilder.CreateIndex( - name: "IX_UpdateInfoBaseTaskV8Configuration_UpdateTasksId", - table: "UpdateInfoBaseTaskV8Configuration", + name: "IX_UpdateInfoBaseTaskV8File_UpdateTasksId", + table: "UpdateInfoBaseTaskV8File", column: "UpdateTasksId"); } @@ -382,7 +489,7 @@ namespace OnecMonitor.Server.Migrations name: "UpdateInfoBaseTaskLogItems"); migrationBuilder.DropTable( - name: "UpdateInfoBaseTaskV8Configuration"); + name: "UpdateInfoBaseTaskV8File"); migrationBuilder.DropTable( name: "LogTemplates"); @@ -393,20 +500,29 @@ namespace OnecMonitor.Server.Migrations migrationBuilder.DropTable( name: "InfoBases"); - migrationBuilder.DropTable( - name: "Configurations"); - migrationBuilder.DropTable( name: "UpdateInfoBaseTasks"); migrationBuilder.DropTable( name: "Clusters"); + migrationBuilder.DropTable( + name: "MaintenanceTasks"); + migrationBuilder.DropTable( name: "Agents"); migrationBuilder.DropTable( name: "Credentials"); + + migrationBuilder.DropTable( + name: "MaintenanceStepNodes"); + + migrationBuilder.DropTable( + name: "MaintenanceStep"); + + migrationBuilder.DropTable( + name: "V8Files"); } } } diff --git a/onecmonitor-server/Migrations/AppDbContextModelSnapshot.cs b/onecmonitor-server/Migrations/AppDbContextModelSnapshot.cs index 9e0fa84..2486979 100644 --- a/onecmonitor-server/Migrations/AppDbContextModelSnapshot.cs +++ b/onecmonitor-server/Migrations/AppDbContextModelSnapshot.cs @@ -167,6 +167,9 @@ namespace OnecMonitor.Server.Migrations .IsRequired() .HasColumnType("TEXT"); + b.Property("MaintenanceTaskId") + .HasColumnType("TEXT"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT"); @@ -181,6 +184,8 @@ namespace OnecMonitor.Server.Migrations b.HasIndex("CredentialsId"); + b.HasIndex("MaintenanceTaskId"); + b.ToTable("InfoBases"); }); @@ -203,6 +208,87 @@ namespace OnecMonitor.Server.Migrations b.ToTable("LogTemplates"); }); + modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStep", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("FileId") + .HasColumnType("TEXT"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FileId"); + + b.ToTable("MaintenanceStep"); + }); + + modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("LeftNodeId") + .HasColumnType("TEXT"); + + b.Property("RightNodeId") + .HasColumnType("TEXT"); + + b.Property("StepId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LeftNodeId"); + + b.HasIndex("RightNodeId"); + + b.HasIndex("StepId"); + + b.ToTable("MaintenanceStepNodes"); + }); + + modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RootNodeId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RootNodeId"); + + b.ToTable("MaintenanceTasks"); + }); + modelBuilder.Entity("OnecMonitor.Server.Models.TechLogFilter", b => { b.Property("Id") @@ -341,7 +427,7 @@ namespace OnecMonitor.Server.Migrations b.ToTable("UpdateInfoBaseTaskLogItems"); }); - modelBuilder.Entity("OnecMonitor.Server.Models.V8Configuration", b => + modelBuilder.Entity("OnecMonitor.Server.Models.V8File", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -357,6 +443,9 @@ namespace OnecMonitor.Server.Migrations b.Property("IsExtension") .HasColumnType("INTEGER"); + b.Property("IsExternalDataProcessor") + .HasColumnType("INTEGER"); + b.Property("IsUpdate") .HasColumnType("INTEGER"); @@ -370,22 +459,22 @@ namespace OnecMonitor.Server.Migrations b.HasKey("Id"); - b.ToTable("Configurations"); + b.ToTable("V8Files"); }); - modelBuilder.Entity("UpdateInfoBaseTaskV8Configuration", b => + modelBuilder.Entity("UpdateInfoBaseTaskV8File", b => { - b.Property("ConfigurationsId") + b.Property("FilesId") .HasColumnType("TEXT"); b.Property("UpdateTasksId") .HasColumnType("TEXT"); - b.HasKey("ConfigurationsId", "UpdateTasksId"); + b.HasKey("FilesId", "UpdateTasksId"); b.HasIndex("UpdateTasksId"); - b.ToTable("UpdateInfoBaseTaskV8Configuration"); + b.ToTable("UpdateInfoBaseTaskV8File"); }); modelBuilder.Entity("AgentTechLogSeance", b => @@ -464,11 +553,58 @@ namespace OnecMonitor.Server.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceTask", null) + .WithMany("InfoBases") + .HasForeignKey("MaintenanceTaskId"); + b.Navigation("Cluster"); b.Navigation("Credentials"); }); + modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStep", b => + { + b.HasOne("OnecMonitor.Server.Models.V8File", "File") + .WithMany() + .HasForeignKey("FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", b => + { + b.HasOne("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", "LeftNode") + .WithMany() + .HasForeignKey("LeftNodeId"); + + b.HasOne("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", "RightNode") + .WithMany() + .HasForeignKey("RightNodeId"); + + b.HasOne("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStep", "Step") + .WithMany() + .HasForeignKey("StepId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LeftNode"); + + b.Navigation("RightNode"); + + b.Navigation("Step"); + }); + + modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceTask", b => + { + b.HasOne("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", "RootNode") + .WithMany() + .HasForeignKey("RootNodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RootNode"); + }); + modelBuilder.Entity("OnecMonitor.Server.Models.UpdateInfoBaseTaskLogItem", b => { b.HasOne("OnecMonitor.Server.Models.InfoBase", "InfoBase") @@ -488,11 +624,11 @@ namespace OnecMonitor.Server.Migrations b.Navigation("Task"); }); - modelBuilder.Entity("UpdateInfoBaseTaskV8Configuration", b => + modelBuilder.Entity("UpdateInfoBaseTaskV8File", b => { - b.HasOne("OnecMonitor.Server.Models.V8Configuration", null) + b.HasOne("OnecMonitor.Server.Models.V8File", null) .WithMany() - .HasForeignKey("ConfigurationsId") + .HasForeignKey("FilesId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -520,6 +656,11 @@ namespace OnecMonitor.Server.Migrations b.Navigation("InfoBases"); }); + modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceTask", b => + { + b.Navigation("InfoBases"); + }); + modelBuilder.Entity("OnecMonitor.Server.Models.UpdateInfoBaseTask", b => { b.Navigation("Log"); diff --git a/onecmonitor-server/Models/MaintenanceStep.cs b/onecmonitor-server/Models/MaintenanceStep.cs deleted file mode 100644 index a0894c8..0000000 --- a/onecmonitor-server/Models/MaintenanceStep.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Configuration; - -namespace OnecMonitor.Server.Models; - -public abstract class MaintenanceStep -{ - public MaintenanceStepKind Kind { get; set; } - public Configuration? Configuration { get; set; } - public string AccessCode { get; set; } = string.Empty; - public string Message { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/onecmonitor-server/Models/MaintenanceTasks/MaintenanceStep.cs b/onecmonitor-server/Models/MaintenanceTasks/MaintenanceStep.cs new file mode 100644 index 0000000..7c812aa --- /dev/null +++ b/onecmonitor-server/Models/MaintenanceTasks/MaintenanceStep.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace OnecMonitor.Server.Models.MaintenanceTasks; + +public class MaintenanceStep : DatabaseObject +{ + public MaintenanceStepKind Kind { get; set; } + + [MaxLength(20)] + public string AccessCode { get; set; } = string.Empty; + [MaxLength(200)] + public string Message { get; set; } = string.Empty; + public Guid? FileId { get; set; } + + public V8File? File { get; set; } = null!; +} \ No newline at end of file diff --git a/onecmonitor-server/Models/MaintenanceStepKind.cs b/onecmonitor-server/Models/MaintenanceTasks/MaintenanceStepKind.cs similarity index 93% rename from onecmonitor-server/Models/MaintenanceStepKind.cs rename to onecmonitor-server/Models/MaintenanceTasks/MaintenanceStepKind.cs index f717e9c..8870161 100644 --- a/onecmonitor-server/Models/MaintenanceStepKind.cs +++ b/onecmonitor-server/Models/MaintenanceTasks/MaintenanceStepKind.cs @@ -1,7 +1,7 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; -namespace OnecMonitor.Server.Models; +namespace OnecMonitor.Server.Models.MaintenanceTasks; public enum MaintenanceStepKind { diff --git a/onecmonitor-server/Models/MaintenanceTasks/MaintenanceStepNode.cs b/onecmonitor-server/Models/MaintenanceTasks/MaintenanceStepNode.cs new file mode 100644 index 0000000..b9b85bd --- /dev/null +++ b/onecmonitor-server/Models/MaintenanceTasks/MaintenanceStepNode.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace OnecMonitor.Server.Models.MaintenanceTasks; + +public class MaintenanceStepNode : DatabaseObject +{ + public MaintenanceStepNodeKind Kind { get; set; } + + public Guid? LeftNodeId { get; set; } + public Guid? RightNodeId { get; set; } + public Guid StepId { get; set; } + + [ForeignKey(nameof(LeftNodeId))] + public MaintenanceStepNode LeftNode { get; set; } = null!; + [ForeignKey(nameof(RightNodeId))] + public MaintenanceStepNode RightNode { get; set; } = null!; + [ForeignKey(nameof(StepId))] + public MaintenanceStep Step { get; set; } = null!; +} \ No newline at end of file diff --git a/onecmonitor-server/Models/MaintenanceTasks/MaintenanceStepNodeKind.cs b/onecmonitor-server/Models/MaintenanceTasks/MaintenanceStepNodeKind.cs new file mode 100644 index 0000000..310e5d3 --- /dev/null +++ b/onecmonitor-server/Models/MaintenanceTasks/MaintenanceStepNodeKind.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace OnecMonitor.Server.Models.MaintenanceTasks; + +public enum MaintenanceStepNodeKind +{ + [Display(Name = "Простой")] + Simple, + [Display(Name = "Попытка/Исключение")] + TryCatch +} \ No newline at end of file diff --git a/onecmonitor-server/Models/MaintenanceTasks/MaintenanceTask.cs b/onecmonitor-server/Models/MaintenanceTasks/MaintenanceTask.cs new file mode 100644 index 0000000..a39624b --- /dev/null +++ b/onecmonitor-server/Models/MaintenanceTasks/MaintenanceTask.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace OnecMonitor.Server.Models.MaintenanceTasks; + +public class MaintenanceTask : DatabaseObject +{ + [MaxLength(200)] + public string Description { get; set; } = string.Empty; + public Guid RootNodeId { get; set; } + + [ForeignKey(nameof(RootNodeId))] + public MaintenanceStepNode RootNode { get; set; } = null!; + + public virtual List InfoBases { get; set; } = []; +} \ No newline at end of file diff --git a/onecmonitor-server/Models/UpdateInfoBaseTask.cs b/onecmonitor-server/Models/UpdateInfoBaseTask.cs index eca91ac..5af8e87 100644 --- a/onecmonitor-server/Models/UpdateInfoBaseTask.cs +++ b/onecmonitor-server/Models/UpdateInfoBaseTask.cs @@ -12,7 +12,7 @@ public class UpdateInfoBaseTask : DatabaseObject public string Description { get; set; } = string.Empty; public DateTime StartDateTime { get; set; } = DateTime.MinValue; - public virtual List Configurations { get; set; } = []; + public virtual List Files { get; set; } = []; public virtual List InfoBases { get; set; } = []; public virtual List Log { get; set; } = []; } \ No newline at end of file diff --git a/onecmonitor-server/Models/V8Configuration.cs b/onecmonitor-server/Models/V8File.cs similarity index 80% rename from onecmonitor-server/Models/V8Configuration.cs rename to onecmonitor-server/Models/V8File.cs index 9fb339e..583de1c 100644 --- a/onecmonitor-server/Models/V8Configuration.cs +++ b/onecmonitor-server/Models/V8File.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; namespace OnecMonitor.Server.Models; -public class V8Configuration : DatabaseObject +public class V8File : DatabaseObject { public string Name { get; set; } = string.Empty; public string Version { get; set; } = string.Empty; @@ -10,6 +10,7 @@ public class V8Configuration : DatabaseObject public bool IsUpdate { get; set; } = false; public bool IsExtension { get; set; } = false; public bool IsConfiguration { get; set; } = false; + public bool IsExternalDataProcessor { get; set; } = false; public virtual List UpdateTasks { get; set; } = []; @@ -23,6 +24,8 @@ public class V8Configuration : DatabaseObject postfix = "расширение"; else if (IsConfiguration) postfix = "конфигурация"; + else if (IsExternalDataProcessor) + postfix = "внешняя обработка"; return $"{Name} ({Version}, {postfix})"; } diff --git a/onecmonitor-server/Program.cs b/onecmonitor-server/Program.cs index ca552a9..5db46db 100644 --- a/onecmonitor-server/Program.cs +++ b/onecmonitor-server/Program.cs @@ -14,10 +14,13 @@ using OnecMonitor.Server.AutoMapper; using OnecMonitor.Server.Models; var builder = WebApplication.CreateBuilder(args); + builder.Services.AddWindowsService(options => { options.ServiceName = "OnecMonitor"; }); +builder.Services.AddSystemd(); + builder.WebHost.ConfigureKestrel((context, options) => { options.Limits.MaxRequestBodySize = 2000 * 1024 * 1024; diff --git a/onecmonitor-server/Scripts/MaintenanceTask/editStepDialog.ts b/onecmonitor-server/Scripts/MaintenanceTask/editStepDialog.ts new file mode 100644 index 0000000..50cd478 --- /dev/null +++ b/onecmonitor-server/Scripts/MaintenanceTask/editStepDialog.ts @@ -0,0 +1,122 @@ +import * as bootstrap from "bootstrap"; +import {Modal} from "bootstrap"; +import * as uuid from "uuid"; +import { StepValidationResult } from './stepValidationResult' + +export class EditStepDialog { + private dialogElement: HTMLDivElement; + private dialogBody: HTMLDivElement; + private modal: Modal; + + private onSaveCallback: (step: any) => void = undefined; + + constructor(element: HTMLDivElement) { + this.dialogElement = element; + this.modal = new bootstrap.Modal(element); + this.dialogBody = this.dialogElement.querySelector('.modal-body'); + + this.dialogElement.querySelector('#save-btn').onclick = async () => { + try { + const result = await this.validateStepForm(); + + if (result.isValid) { + this.close(); + this.onSaveCallback(result.payload); + } else + this.dialogBody.innerHTML = result.payload; + } catch (e) { + alert(e); + } + }; + } + + public open(onSave: (step: any) => void, step: any | undefined = undefined) { + this.onSaveCallback = onSave; + + if (step == undefined) + step = { + Id: uuid.v4() + }; + + this.getEditStepForm(step).then(data => { + this.dialogBody.innerHTML = data; + + const select = this.dialogBody.querySelector("select[name='Kind']"); + select.onchange = async () => { + await this.updateStepForm(); + }; + + this.modal.show(); + }); + } + + public close(): void { + this.modal.hide(); + } + + private async updateStepForm() { + const formData = new FormData(this.dialogBody.querySelector('form')); + + const response = await fetch('/MaintenanceTasks/UpdateStep', { + method: 'POST', + body: formData + }); + + if (response.ok) { + this.dialogBody.innerHTML = await response.text(); + + const select = this.dialogBody.querySelector("select[name='Kind']"); + select.onchange = async () => { + await this.updateStepForm(); + }; + } else + alert(`Failed to update step form: ${response.statusText}`); + } + + private async getEditStepForm(step: any | undefined = undefined): Promise { + return new Promise(async (resolve, reject) => { + const body = step != undefined ? JSON.stringify(step) : ""; + + const response = await fetch("/MaintenanceTasks/EditStep", { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: body, + }); + + if (response.ok) + resolve(await response.text()); + else + reject(`Failed to get step kind view: ${response.statusText}`); + }); + } + + private async validateStepForm(): Promise { + return new Promise(async (resolve, reject) => { + const formData = new FormData(this.dialogBody.querySelector('form')); + + const validateResponse = await fetch('/MaintenanceTasks/ValidateStep', { + method: 'POST', + body: formData + }); + + if (validateResponse.status == 200) { + //editStepModal.hide() + + const result = new StepValidationResult(); + result.isValid = true; + result.payload = await validateResponse.json(); + + resolve(result); + } else if (validateResponse.status == 400) { + const result = new StepValidationResult(); + result.isValid = false; + result.payload = await validateResponse.text(); + + resolve(result); + } else + reject(`Failed to validate step: ${validateResponse.statusText}`); + }); + } +} \ No newline at end of file diff --git a/onecmonitor-server/Scripts/MaintenanceTask/maintenanceStepNodeKind.ts b/onecmonitor-server/Scripts/MaintenanceTask/maintenanceStepNodeKind.ts new file mode 100644 index 0000000..dc3251f --- /dev/null +++ b/onecmonitor-server/Scripts/MaintenanceTask/maintenanceStepNodeKind.ts @@ -0,0 +1,4 @@ +export enum MaintenanceStepNodeKind { + Simple, + TryCatch +} \ No newline at end of file diff --git a/onecmonitor-server/Scripts/MaintenanceTask/nodesGraph.ts b/onecmonitor-server/Scripts/MaintenanceTask/nodesGraph.ts new file mode 100644 index 0000000..7db4188 --- /dev/null +++ b/onecmonitor-server/Scripts/MaintenanceTask/nodesGraph.ts @@ -0,0 +1,32 @@ +import {EditStepDialog} from "./editStepDialog"; +import {NodesGraphOptions} from "./nodesGraphOptions"; +import {NodesGraphActionsDialog} from './nodesGraphActionsDialog' +import {StepsEditorHelper} from "./stepsEditorHelper"; + +export class NodesGraph { + private nodesGraphActionsDialog: NodesGraphActionsDialog + private editStepDialog: EditStepDialog + private readonly stepsEditorHelper: StepsEditorHelper + + constructor(options: NodesGraphOptions) { + + this.stepsEditorHelper = new StepsEditorHelper(options.nodesInputElement); + this.stepsEditorHelper.init().then(() => { + this.editStepDialog = new EditStepDialog(options.editStepModalElement); + this.nodesGraphActionsDialog = new NodesGraphActionsDialog( + this.editStepDialog, + this.stepsEditorHelper, + options.nodeGraphActionsElement + ); + + options.bodyElement.addEventListener('click', async e => { + if (options.nodesInputElement.value == undefined || options.nodesInputElement.value == "") + this.nodesGraphActionsDialog.show(); + }); + }); + } + + public handleNodeClick(nodeId: string) { + this.nodesGraphActionsDialog.show(nodeId); + } +} \ No newline at end of file diff --git a/onecmonitor-server/Scripts/MaintenanceTask/nodesGraphActionsDialog.ts b/onecmonitor-server/Scripts/MaintenanceTask/nodesGraphActionsDialog.ts new file mode 100644 index 0000000..8496ec6 --- /dev/null +++ b/onecmonitor-server/Scripts/MaintenanceTask/nodesGraphActionsDialog.ts @@ -0,0 +1,104 @@ +import {EditStepDialog} from "./editStepDialog"; +import * as uuid from "uuid"; +import {MaintenanceStepNodeKind} from "./maintenanceStepNodeKind"; +import {StepsEditorHelper} from './stepsEditorHelper' + +export class NodesGraphActionsDialog { + private editStepDialog: EditStepDialog; + private stepsEditorHelper: StepsEditorHelper; + private dialogElement: HTMLDivElement; + + constructor(editStepDialog: EditStepDialog, stepsEditorHelper: StepsEditorHelper, popupElement: HTMLDivElement) { + this.editStepDialog = editStepDialog; + this.stepsEditorHelper = stepsEditorHelper; + this.dialogElement = popupElement; + } + + public show(nodeId: string | undefined = undefined): void { + const root = this.stepsEditorHelper.getRootNode(); + const node = nodeId == undefined ? undefined : this.stepsEditorHelper.findNode(nodeId, root); + + const editBtn = this.dialogElement.querySelector('#edit-step-btn'); + editBtn.hidden = nodeId == undefined; + editBtn.onclick = async () => { + this.dialogElement.hidePopover(); + this.editStepDialog.open(step => { + node.Step = step; + node.StepId = step.id; + }, node.Step); + } + + const deleteStepBtn = this.dialogElement.querySelector('#delete-step-btn'); + deleteStepBtn.hidden = nodeId == undefined; + deleteStepBtn.onclick = async () => { + this.dialogElement.hidePopover(); + + if (root.Id == node.Id) + this.stepsEditorHelper.saveNode(undefined); + else { + this.stepsEditorHelper.deleteNode(root, node); + this.stepsEditorHelper.saveNode(root); + } + + await this.stepsEditorHelper.redrawNodes(); + } + + this.dialogElement.querySelector('#add-step-btn').onclick = () => { + this.dialogElement.hidePopover(); + this.addStep(MaintenanceStepNodeKind.Simple, nodeId); + } + + this.dialogElement.querySelector('#add-binary-step-btn').onclick = () => { + this.dialogElement.hidePopover(); + this.addStep(MaintenanceStepNodeKind.TryCatch, nodeId); + } + + const addErrorStepBtn = this.dialogElement.querySelector('#add-error-step-btn'); + addErrorStepBtn.hidden = nodeId == undefined || node.Kind == MaintenanceStepNodeKind.Simple; + addErrorStepBtn.onclick = async () => { + this.dialogElement.hidePopover(); + this.addCatchNode(root, node, MaintenanceStepNodeKind.Simple); + } + + const addErrorBinaryStepBtn = this.dialogElement.querySelector('#add-error-binary-step-btn'); + addErrorBinaryStepBtn.hidden = nodeId == undefined || node.Kind == MaintenanceStepNodeKind.Simple; + addErrorBinaryStepBtn.onclick = async () => { + this.dialogElement.hidePopover(); + this.addCatchNode(root, node, MaintenanceStepNodeKind.TryCatch); + } + + this.dialogElement.showPopover() + } + + private addStep(kind: MaintenanceStepNodeKind, nodeId: string | undefined = undefined) { + if (nodeId == undefined) + this.editStepDialog.open(async step => { + await this.stepsEditorHelper.addRootNode({ + Id: uuid.v4(), + Kind: kind, + Step: step, + StepId: step.id + }); + }); + else + this.editStepDialog.open(async step => { + await this.stepsEditorHelper.addLeftNode(nodeId, { + Id: uuid.v4(), + Kind: kind, + Step: step, + StepId: step.id + }); + }); + } + + private addCatchNode(root: any, parent: any, kind: MaintenanceStepNodeKind) { + this.editStepDialog.open(async step => { + await this.stepsEditorHelper.addRightNode(root, parent, { + Id: uuid.v4(), + Kind: kind, + Step: step, + StepId: step.id + }); + }); + } +} \ No newline at end of file diff --git a/onecmonitor-server/Scripts/MaintenanceTask/nodesGraphOptions.ts b/onecmonitor-server/Scripts/MaintenanceTask/nodesGraphOptions.ts new file mode 100644 index 0000000..1caca9b --- /dev/null +++ b/onecmonitor-server/Scripts/MaintenanceTask/nodesGraphOptions.ts @@ -0,0 +1,6 @@ +export class NodesGraphOptions { + bodyElement: HTMLDivElement + nodeGraphActionsElement: HTMLDivElement + editStepModalElement: HTMLDivElement + nodesInputElement: HTMLInputElement +} \ No newline at end of file diff --git a/onecmonitor-server/Scripts/MaintenanceTask/stepValidationResult.ts b/onecmonitor-server/Scripts/MaintenanceTask/stepValidationResult.ts new file mode 100644 index 0000000..1c2b47d --- /dev/null +++ b/onecmonitor-server/Scripts/MaintenanceTask/stepValidationResult.ts @@ -0,0 +1,4 @@ +export class StepValidationResult { + isValid: boolean; + payload: any; +} \ No newline at end of file diff --git a/onecmonitor-server/Scripts/MaintenanceTask/stepsEditorHelper.ts b/onecmonitor-server/Scripts/MaintenanceTask/stepsEditorHelper.ts new file mode 100644 index 0000000..f82ac85 --- /dev/null +++ b/onecmonitor-server/Scripts/MaintenanceTask/stepsEditorHelper.ts @@ -0,0 +1,165 @@ +import Mermaid from "mermaid"; + +export class StepsEditorHelper { + private schemaInput: HTMLInputElement; + + constructor(input: HTMLInputElement) { + this.schemaInput = input; + } + + public async init() { + Mermaid.initialize({ + theme: 'dark', + securityLevel: 'loose', + startOnLoad: false, + darkMode: true + }) + + await this.redrawNodes() + } + + findNode(id: string, node: any): any | undefined { + if (node.Id == id) + return node; + + const currentNode = node; + + let foundNode = undefined; + + if (currentNode.hasOwnProperty('RightNode') && currentNode.RightNode != undefined) + foundNode = this.findNode(id, currentNode.RightNode); + + if (foundNode == undefined && currentNode.hasOwnProperty('LeftNode') && currentNode.LeftNode != undefined) + foundNode = this.findNode(id, currentNode.LeftNode); + + return foundNode; + } + + deleteNode(node: any, removingNode: any) { + if (node.hasOwnProperty('RightNode') && node.RightNode != undefined) + { + if (node.RightNode.Id == removingNode.Id) + { + delete node['RightNode']; + delete node['RightNodeId']; + + return; + } + else + this.deleteNode(node.RightNode, removingNode); + } + + if (node.hasOwnProperty('LeftNode') && node.LeftNode != undefined) + { + if (node.LeftNode.Id == removingNode.Id) + { + delete node['LeftNode']; + delete node['LeftNodeId']; + + return; + } + else + this.deleteNode(node.LeftNode, removingNode); + } + } + + async addRootNode(node: any) { + this.saveNode(node); + await this.redrawNodes(); + } + + async addLeftNode(parentId: string, node: any) { + const root = this.getRootNode(); + const parent = this.findNode(parentId, root); + parent.LeftNodeId = node.Id; + parent.LeftNode = node; + + this.saveNode(root); + await this.redrawNodes(); + } + + async addRightNode(root: any, parent: any, node: any) { + parent.RightNodeId = node.Id; + parent.RightNode = node; + + this.saveNode(root); + await this.redrawNodes(); + } + + saveNode(node: any | undefined) { + if (node == undefined) + this.schemaInput.value = ''; + else + this.schemaInput.value = JSON.stringify(node); + } + + getRootNode(): any | undefined { + if (this.schemaInput.value == "") + return undefined; + else + return JSON.parse(this.schemaInput.value); + } + + private nodeToGraphDefinition(currentNode: any): string { + let currentNodeText = ""; + + let currentNodeLeft = currentNode.Id; + + // is try/catch node + if (currentNode.Kind == 1) { + const node = currentNode; + + currentNodeLeft = `${currentNodeLeft}{${currentNode.Step.title}}` + + let needSetCurrentNodeText = true; + + if (node.hasOwnProperty('LeftNode') && node.LeftNode != undefined) { + currentNodeText += currentNodeLeft + " -->|Успешно| " + this.nodeToGraphDefinition(node.LeftNode) + '\r'; + needSetCurrentNodeText = false; + } + + if (node.hasOwnProperty('RightNode') && node.RightNode != undefined) { + currentNodeText += currentNodeLeft + " -->|Ошибка| " + this.nodeToGraphDefinition(node.RightNode) + '\r'; + needSetCurrentNodeText = false; + } + + if (needSetCurrentNodeText) + currentNodeText = currentNodeLeft; + } else { + const node = currentNode; + + currentNodeText = `${currentNodeLeft}[${currentNode.Step.title}]` + + if (node.hasOwnProperty('LeftNode') && node.LeftNode != undefined) + currentNodeText += " --> " + this.nodeToGraphDefinition(node.LeftNode) + '\r'; + } + + if (currentNodeText[currentNodeText.length - 1] != '\r') + currentNodeText += '\r'; + + currentNodeText += `click ${currentNode.Id} OM.clickNode\r`; + return currentNodeText; + } + + async redrawNodes() { + const root = this.getRootNode(); + const nodesDefinition = root == undefined ? "" : this.nodeToGraphDefinition(root); + + let graphDefinition = "flowchart TD\n" + nodesDefinition; + await this.redrawGraph(graphDefinition); + } + + private async redrawGraph(definition: string) { + document.querySelector('.mermaid')?.remove() + + const graphBody = document.querySelector('#graphBody'); + + const mermaidContainer = document.createElement('div'); + mermaidContainer.classList.add('mermaid'); + mermaidContainer.classList.add('text-center'); + mermaidContainer.textContent = definition; + graphBody.appendChild(mermaidContainer); + + await Mermaid.run() + } +} \ No newline at end of file diff --git a/onecmonitor-server/Scripts/index.ts b/onecmonitor-server/Scripts/index.ts index 61192cc..7f70caa 100644 --- a/onecmonitor-server/Scripts/index.ts +++ b/onecmonitor-server/Scripts/index.ts @@ -6,14 +6,21 @@ import '../node_modules/bootstrap-icons/font/bootstrap-icons.css'; //modules import '../node_modules/bootstrap/dist/js/bootstrap.bundle.min' -require('../Scripts/itemSelectionDialog') -require('../Scripts/deleteDialog') -require('../Scripts/techLog') -require('../Scripts/infoBasesUpdatingTaskLog') -require('./maintenanceTasks') +require('./itemSelectionDialog') +require('./deleteDialog') +require('./techLog') +require('./infoBasesUpdatingTaskLog') +require('./infoBasesUpdateTask') -export * from '../Scripts/itemSelectionDialog' -export * from '../Scripts/deleteDialog' -export * from '../Scripts/techLog' -export * from '../Scripts/infoBasesUpdatingTaskLog' -export * from './maintenanceTasks' \ No newline at end of file +export * from './itemSelectionDialog' +export * from './deleteDialog' +export * from './techLog' +export * from './infoBasesUpdatingTaskLog' +export * from './infoBasesUpdateTask' +export {EditStepDialog} from "./MaintenanceTask/editStepDialog"; +export {StepValidationResult} from "./MaintenanceTask/stepValidationResult"; +export {MaintenanceStepNodeKind} from "./MaintenanceTask/maintenanceStepNodeKind"; +export {NodesGraphOptions} from "./MaintenanceTask/nodesGraphOptions"; +export {NodesGraph} from "./MaintenanceTask/nodesGraph"; +export {NodesGraphActionsDialog} from "./MaintenanceTask/nodesGraphActionsDialog"; +export {StepsEditorHelper} from "./MaintenanceTask/stepsEditorHelper"; \ No newline at end of file diff --git a/onecmonitor-server/Scripts/infoBasesUpdateTask.ts b/onecmonitor-server/Scripts/infoBasesUpdateTask.ts new file mode 100644 index 0000000..785f2c5 --- /dev/null +++ b/onecmonitor-server/Scripts/infoBasesUpdateTask.ts @@ -0,0 +1,32 @@ +import {NodesGraph} from "./MaintenanceTask/nodesGraph"; + +let nodesGraph: NodesGraph = undefined; + +export async function initStepsEditor() { + nodesGraph = new NodesGraph({ + nodesInputElement: getSchemaNodesElement(), + editStepModalElement: getStepEditDialogElement(), + nodeGraphActionsElement: getNodeActionsElement(), + bodyElement: getGraphBodyElement() + }); +} + +export function clickNode(nodeId: string) { + nodesGraph.handleNodeClick(nodeId); +} + +function getNodeActionsElement(): HTMLDivElement { + return document.getElementById('node-actions') as HTMLDivElement; +} + +function getGraphBodyElement(): HTMLDivElement { + return document.getElementById('graphBody') as HTMLDivElement; +} + +function getSchemaNodesElement(): HTMLInputElement { + return document.getElementById('schema-nodes') as HTMLInputElement; +} + +function getStepEditDialogElement(): HTMLDivElement { + return document.getElementById('edit-step-dialog') as HTMLDivElement; +} \ No newline at end of file diff --git a/onecmonitor-server/Services/AgentConnection.cs b/onecmonitor-server/Services/AgentConnection.cs index 3f7957b..a880387 100644 --- a/onecmonitor-server/Services/AgentConnection.cs +++ b/onecmonitor-server/Services/AgentConnection.cs @@ -15,6 +15,8 @@ using AutoMapper; using AutoMapper.QueryableExtensions; using OnecMonitor.Server.Helpers; using OneSTools.Common.Platform; +using OneSTools.Common.Platform.RemoteAdministration; +using OneSTools.Common.Platform.Services; namespace OnecMonitor.Server.Services { @@ -40,6 +42,7 @@ namespace OnecMonitor.Server.Services public event AgentDisconnectedHandler? AgentDisconnected; public AgentConnection(Socket socket, TechLogProcessor techLogProcessor, IServiceProvider serviceProvider) + : base(serviceProvider.GetRequiredService>()) { Socket = socket; @@ -315,7 +318,7 @@ namespace OnecMonitor.Server.Services { var task = await _appDbContext.UpdateInfoBaseTasks .Where(c => c.InfoBases.Any(i => i.Cluster.Agent.Id == AgentInstance!.Id)) - .Include(c => c.Configurations) + .Include(c => c.Files) .Include(c => c.InfoBases) .ThenInclude(c => c.Credentials) .Include(c => c.InfoBases) diff --git a/onecmonitor-server/ViewModels/Agents/AgentEditViewModel.cs b/onecmonitor-server/ViewModels/Agents/AgentEditViewModel.cs index 58f098d..004b85b 100644 --- a/onecmonitor-server/ViewModels/Agents/AgentEditViewModel.cs +++ b/onecmonitor-server/ViewModels/Agents/AgentEditViewModel.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using OnecMonitor.Server.Models; using OneSTools.Common.Platform; +using OneSTools.Common.Platform.Services; namespace OnecMonitor.Server.ViewModels.Agents; diff --git a/onecmonitor-server/ViewModels/Configurations/ConfigurationsIndexViewModel.cs b/onecmonitor-server/ViewModels/Configurations/ConfigurationsIndexViewModel.cs deleted file mode 100644 index 1727efd..0000000 --- a/onecmonitor-server/ViewModels/Configurations/ConfigurationsIndexViewModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace OnecMonitor.Server.ViewModels.Configurations; - -public class ConfigurationsIndexViewModel -{ - public List Items { get; init; } = []; -} \ No newline at end of file diff --git a/onecmonitor-server/ViewModels/Maintenance/MaintenanceEditViewModel.cs b/onecmonitor-server/ViewModels/Maintenance/MaintenanceEditViewModel.cs deleted file mode 100644 index 41c1a72..0000000 --- a/onecmonitor-server/ViewModels/Maintenance/MaintenanceEditViewModel.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; -using Microsoft.AspNetCore.Mvc.Rendering; -using OnecMonitor.Server.Models; - -namespace OnecMonitor.Server.ViewModels.Maintenance; - -public class MaintenanceEditViewModel -{ - public Guid Id { get; set; } - public string Description { get; set; } = string.Empty; - - public List InfoBases { get; set; } = []; - [ValidateNever] - public List AvailableInfoBases { get; set; } = []; - - public List MaintenanceSteps { get; set; } = []; - - [ValidateNever] - public SelectList MaintenanceStepKinds { get; set; } = null!; - - [ValidateNever] - public SelectList ExternalDataProcessors { get; set; } = null!; - - [ValidateNever] - public SelectList Extensions { get; set; } = null!; - - [ValidateNever] - public SelectList ConfigUpdates { get; set; } = null!; - - [ValidateNever] - public SelectList Configs { get; set; } = null!; -} \ No newline at end of file diff --git a/onecmonitor-server/ViewModels/Maintenance/MaintenanceIndexViewModel.cs b/onecmonitor-server/ViewModels/Maintenance/MaintenanceIndexViewModel.cs deleted file mode 100644 index 9622ea7..0000000 --- a/onecmonitor-server/ViewModels/Maintenance/MaintenanceIndexViewModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace OnecMonitor.Server.ViewModels.Maintenance; - -public class MaintenanceIndexViewModel -{ - public List Items { get; set; } = []; -} \ No newline at end of file diff --git a/onecmonitor-server/ViewModels/Maintenance/MaintenanceListItemViewModel.cs b/onecmonitor-server/ViewModels/Maintenance/MaintenanceListItemViewModel.cs deleted file mode 100644 index df0bdd9..0000000 --- a/onecmonitor-server/ViewModels/Maintenance/MaintenanceListItemViewModel.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace OnecMonitor.Server.ViewModels.Maintenance; - -public class MaintenanceListItemViewModel -{ - public Guid Id { get; set; } - public string Description { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceStepNodeViewModel.cs b/onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceStepNodeViewModel.cs new file mode 100644 index 0000000..96d1bea --- /dev/null +++ b/onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceStepNodeViewModel.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using OnecMonitor.Server.Models.MaintenanceTasks; + +namespace OnecMonitor.Server.ViewModels.MaintenanceTasks; + +public class MaintenanceStepNodeViewModel +{ + public Guid Id { get; set; } + public MaintenanceStepNodeKind Kind { get; set; } + + [ValidateNever] + public Guid? LeftNodeId { get; set; } + [ValidateNever] + public Guid? RightNodeId { get; set; } + + public Guid StepId { get; set; } + + public MaintenanceStepNodeViewModel? LeftNode { get; set; } = null!; + public MaintenanceStepNodeViewModel? RightNode { get; set; } = null!; + + public MaintenanceStepViewModel Step { get; set; } = null!; +} \ No newline at end of file diff --git a/onecmonitor-server/ViewModels/Maintenance/MaintenanceStepViewModel.cs b/onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceStepViewModel.cs similarity index 50% rename from onecmonitor-server/ViewModels/Maintenance/MaintenanceStepViewModel.cs rename to onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceStepViewModel.cs index 2ab1ae6..915cd44 100644 --- a/onecmonitor-server/ViewModels/Maintenance/MaintenanceStepViewModel.cs +++ b/onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceStepViewModel.cs @@ -1,21 +1,35 @@ +using System.Text.Json.Serialization; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.Rendering; +using OnecMonitor.Server.Extensions; +using OnecMonitor.Server.Helpers; using OnecMonitor.Server.Models; +using OnecMonitor.Server.Models.MaintenanceTasks; -namespace OnecMonitor.Server.ViewModels.Maintenance; +namespace OnecMonitor.Server.ViewModels.MaintenanceTasks; public class MaintenanceStepViewModel { + [JsonPropertyName("id")] public Guid Id { get; set; } + [JsonPropertyName("kind")] public MaintenanceStepKind Kind { get; set; } + [ValidateNever] + public SelectList Kinds { get; set; } = null!; [ValidateNever] + [JsonPropertyName("accessCode")] + public string AccessCode { get; set; } = string.Empty; + [ValidateNever] + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; + + [ValidateNever] + [JsonPropertyName("fileId")] public Guid? FileId { get; set; } [ValidateNever] public SelectList Files { get; set; } = null!; - [ValidateNever] - public string AccessCode { get; set; } = string.Empty; - [ValidateNever] - public string Message { get; set; } = string.Empty; + [JsonPropertyName("title")] + public string Title => Kind.GetDisplay(); } \ No newline at end of file diff --git a/onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceTaskEditViewModel.cs b/onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceTaskEditViewModel.cs new file mode 100644 index 0000000..9908a76 --- /dev/null +++ b/onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceTaskEditViewModel.cs @@ -0,0 +1,21 @@ +using System.ComponentModel; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.AspNetCore.Mvc.Rendering; +using Newtonsoft.Json; +using OnecMonitor.Server.Models; + +namespace OnecMonitor.Server.ViewModels.MaintenanceTasks; + +public class MaintenanceTaskEditViewModel +{ + public Guid Id { get; set; } + [DisplayName("Описание")] + public string Description { get; set; } = string.Empty; + + [DisplayName("Информационные базы")] + public List InfoBases { get; set; } = []; + [ValidateNever] + public List AvailableInfoBases { get; set; } = []; + + public string SerializedStepNode { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceTaskListItemViewModel.cs b/onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceTaskListItemViewModel.cs new file mode 100644 index 0000000..4ff5264 --- /dev/null +++ b/onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceTaskListItemViewModel.cs @@ -0,0 +1,7 @@ +namespace OnecMonitor.Server.ViewModels.MaintenanceTasks; + +public class MaintenanceTaskListItemViewModel +{ + public Guid Id { get; set; } + public string Description { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceTasksIndexViewModel.cs b/onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceTasksIndexViewModel.cs new file mode 100644 index 0000000..458c0ba --- /dev/null +++ b/onecmonitor-server/ViewModels/MaintenanceTasks/MaintenanceTasksIndexViewModel.cs @@ -0,0 +1,6 @@ +namespace OnecMonitor.Server.ViewModels.MaintenanceTasks; + +public class MaintenanceTasksIndexViewModel +{ + public List Items { get; set; } = []; +} \ No newline at end of file diff --git a/onecmonitor-server/ViewModels/UpdateInfoBaseTasks/UpdateInfoBaseTaskEditViewModel.cs b/onecmonitor-server/ViewModels/UpdateInfoBaseTasks/UpdateInfoBaseTaskEditViewModel.cs index dd35747..3948ea3 100644 --- a/onecmonitor-server/ViewModels/UpdateInfoBaseTasks/UpdateInfoBaseTaskEditViewModel.cs +++ b/onecmonitor-server/ViewModels/UpdateInfoBaseTasks/UpdateInfoBaseTaskEditViewModel.cs @@ -12,9 +12,9 @@ public class UpdateInfoBaseTaskEditViewModel [ValidateNever] [DisplayName("Конфигурации")] - public List Configurations { get; set; } = []; + public List Files { get; set; } = []; [ValidateNever] - public List AvailableConfigurations { get; set; } = []; + public List AvailableFiles { get; set; } = []; [DisplayName("Информационные базы")] public List InfoBases { get; set; } = []; diff --git a/onecmonitor-server/ViewModels/Configurations/ConfigurationEditViewModel.cs b/onecmonitor-server/ViewModels/V8Files/V8FileEditViewModel.cs similarity index 70% rename from onecmonitor-server/ViewModels/Configurations/ConfigurationEditViewModel.cs rename to onecmonitor-server/ViewModels/V8Files/V8FileEditViewModel.cs index f23c0fc..feb7143 100644 --- a/onecmonitor-server/ViewModels/Configurations/ConfigurationEditViewModel.cs +++ b/onecmonitor-server/ViewModels/V8Files/V8FileEditViewModel.cs @@ -1,8 +1,8 @@ using Microsoft.AspNetCore.Mvc; -namespace OnecMonitor.Server.ViewModels.Configurations; +namespace OnecMonitor.Server.ViewModels.V8Files; -public class ConfigurationEditViewModel +public class V8FileEditViewModel { public Guid Id { get; init; } public string Name { get; set; } = string.Empty; diff --git a/onecmonitor-server/ViewModels/Configurations/ConfigurationListItemViewModel.cs b/onecmonitor-server/ViewModels/V8Files/V8FileListItemViewModel.cs similarity index 75% rename from onecmonitor-server/ViewModels/Configurations/ConfigurationListItemViewModel.cs rename to onecmonitor-server/ViewModels/V8Files/V8FileListItemViewModel.cs index 43e02ac..b62ab99 100644 --- a/onecmonitor-server/ViewModels/Configurations/ConfigurationListItemViewModel.cs +++ b/onecmonitor-server/ViewModels/V8Files/V8FileListItemViewModel.cs @@ -1,6 +1,6 @@ -namespace OnecMonitor.Server.ViewModels.Configurations; +namespace OnecMonitor.Server.ViewModels.V8Files; -public class ConfigurationListItemViewModel +public class V8FileListItemViewModel { public Guid Id { get; init; } public string Name { get; set; } = string.Empty; @@ -17,8 +17,10 @@ public class ConfigurationListItemViewModel return "Обновление"; else if (IsConfiguration) return "Конфигурация"; - else + else if (IsExtension) return "Расширение"; + else + return "Внешняя обработка"; } } } \ No newline at end of file diff --git a/onecmonitor-server/ViewModels/V8Files/V8FilesIndexViewModel.cs b/onecmonitor-server/ViewModels/V8Files/V8FilesIndexViewModel.cs new file mode 100644 index 0000000..ced69b3 --- /dev/null +++ b/onecmonitor-server/ViewModels/V8Files/V8FilesIndexViewModel.cs @@ -0,0 +1,6 @@ +namespace OnecMonitor.Server.ViewModels.V8Files; + +public class V8FilesIndexViewModel +{ + public List Items { get; init; } = []; +} \ No newline at end of file diff --git a/onecmonitor-server/Views/MaintenanceTasks/Edit.cshtml b/onecmonitor-server/Views/MaintenanceTasks/Edit.cshtml new file mode 100644 index 0000000..f4de841 --- /dev/null +++ b/onecmonitor-server/Views/MaintenanceTasks/Edit.cshtml @@ -0,0 +1,101 @@ +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using NuGet.Protocol +@using OnecMonitor.Server.Extensions +@using OnecMonitor.Server.Helpers +@using OnecMonitor.Server.Models.MaintenanceTasks +@using OnecMonitor.Server.ViewModels +@model OnecMonitor.Server.ViewModels.MaintenanceTasks.MaintenanceTaskEditViewModel + +@{ + ViewData["Title"] = "Задача обслуживания"; +} + +

Задача обслуживания

+ +
+
+ + +
+ + + +
+ + + +
+ +
+ @await Html.PartialAsync("SelectItemDialog", new SelectItemDialogViewModel( + Html.DisplayNameFor(c => c.InfoBases), + nameof(Model.InfoBases), + Model.InfoBases, + Model.AvailableInfoBases)) +
+ +
+
+
Схема
+ +
+
+
+ +
+ + + + + +
+
+
Выберите действие
+
+
+ + + + + + +
+
+
+
+ + +@section Scripts +{ + +} diff --git a/onecmonitor-server/Views/MaintenanceTasks/EditStep.cshtml b/onecmonitor-server/Views/MaintenanceTasks/EditStep.cshtml new file mode 100644 index 0000000..24a3ca6 --- /dev/null +++ b/onecmonitor-server/Views/MaintenanceTasks/EditStep.cshtml @@ -0,0 +1,48 @@ +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using OnecMonitor.Server.Extensions +@using OnecMonitor.Server.Models.MaintenanceTasks +@using OnecMonitor.Server.ViewModels +@model OnecMonitor.Server.ViewModels.MaintenanceTasks.MaintenanceStepViewModel + +
+ + + +
+ + + +
+ + @if (Model.Kind == MaintenanceStepKind.LockConnections) + { +
+ + + +
+ +
+ + + +
+ } + + @if (Model is + { + Kind: MaintenanceStepKind.LoadConfiguration or + MaintenanceStepKind.UpdateConfiguration or + MaintenanceStepKind.LoadExtension or + MaintenanceStepKind.StartExternalDataProcessor + }) + { +
+ + + +
+ } + +
+ diff --git a/onecmonitor-server/Views/MaintenanceTasks/Index.cshtml b/onecmonitor-server/Views/MaintenanceTasks/Index.cshtml new file mode 100644 index 0000000..890093a --- /dev/null +++ b/onecmonitor-server/Views/MaintenanceTasks/Index.cshtml @@ -0,0 +1,43 @@ +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using OnecMonitor.Server.ViewModels +@using OnecMonitor.Server.ViewModels.Agents; +@model OnecMonitor.Server.ViewModels.MaintenanceTasks.MaintenanceTasksIndexViewModel + +@{ + ViewData["Title"] = "Задачи обслуживания"; +} + +

Задачи обслуживания

+ + + +
+ + + + + + + + + @foreach (var item in Model.Items) + { + + + + + } + +
Описание
@item.Description + Изменить + Удалить +
+
+ +@await Html.PartialAsync("DeleteDialog", new DeleteDialogViewModel +{ + Controller = "MaintenanceTasks", + Action = "Delete" +}) \ No newline at end of file diff --git a/onecmonitor-server/Views/Shared/_Layout.cshtml b/onecmonitor-server/Views/Shared/_Layout.cshtml index b4a0ead..b269e92 100644 --- a/onecmonitor-server/Views/Shared/_Layout.cshtml +++ b/onecmonitor-server/Views/Shared/_Layout.cshtml @@ -51,8 +51,13 @@
  • - - Конфигурации + + Файлы 1С + +
  • +
  • + + Задачи обслуживания
  • diff --git a/onecmonitor-server/Views/UpdateInfoBaseTasks/Edit.cshtml b/onecmonitor-server/Views/UpdateInfoBaseTasks/Edit.cshtml index 52c6133..eaa3f91 100644 --- a/onecmonitor-server/Views/UpdateInfoBaseTasks/Edit.cshtml +++ b/onecmonitor-server/Views/UpdateInfoBaseTasks/Edit.cshtml @@ -1,4 +1,7 @@ @using Microsoft.AspNetCore.Mvc.TagHelpers +@using OnecMonitor.Server.Extensions +@using OnecMonitor.Server.Helpers +@using OnecMonitor.Server.Models.MaintenanceTasks @using OnecMonitor.Server.ViewModels @model OnecMonitor.Server.ViewModels.UpdateInfoBaseTasks.UpdateInfoBaseTaskEditViewModel @@ -24,10 +27,10 @@
    @await Html.PartialAsync("SelectItemDialog", new SelectItemDialogViewModel( - Html.DisplayNameFor(c => c.Configurations), - nameof(Model.Configurations), - Model.Configurations, - Model.AvailableConfigurations)) + Html.DisplayNameFor(c => c.Files), + nameof(Model.Files), + Model.Files, + Model.AvailableFiles))
    diff --git a/onecmonitor-server/Views/Configurations/Edit.cshtml b/onecmonitor-server/Views/V8Files/Edit.cshtml similarity index 77% rename from onecmonitor-server/Views/Configurations/Edit.cshtml rename to onecmonitor-server/Views/V8Files/Edit.cshtml index d7f50e6..2775e32 100644 --- a/onecmonitor-server/Views/Configurations/Edit.cshtml +++ b/onecmonitor-server/Views/V8Files/Edit.cshtml @@ -1,38 +1,38 @@ -@using OnecMonitor.Server.ViewModels.Configurations; -@model ConfigurationEditViewModel - -@{ - ViewData["Title"] = "Конфигурация"; -} - -

    Конфигурация

    - -
    -
    - - -
    - - - -
    - - - -
    - -
    - - - -
    - - @if (Model.Id == Guid.Empty) - { -
    - - -
    - } - +@using OnecMonitor.Server.ViewModels.V8Files; +@model V8FileEditViewModel + +@{ + ViewData["Title"] = "Файл 1С"; +} + +

    Файл 1С

    + + +
    + + +
    + + + +
    + + + +
    + +
    + + + +
    + + @if (Model.Id == Guid.Empty) + { +
    + + +
    + } +
    \ No newline at end of file diff --git a/onecmonitor-server/Views/Configurations/Index.cshtml b/onecmonitor-server/Views/V8Files/Index.cshtml similarity index 81% rename from onecmonitor-server/Views/Configurations/Index.cshtml rename to onecmonitor-server/Views/V8Files/Index.cshtml index 96e2b29..0190695 100644 --- a/onecmonitor-server/Views/Configurations/Index.cshtml +++ b/onecmonitor-server/Views/V8Files/Index.cshtml @@ -1,45 +1,45 @@ -@using OnecMonitor.Server.ViewModels -@model OnecMonitor.Server.ViewModels.Configurations.ConfigurationsIndexViewModel - -@{ - ViewData["Title"] = "Конфигурации"; -} - -

    Конфигурации

    - - - -
    - - - - - - - - - - - @foreach (var item in Model.Items) - { - - - - - - - } - -
    ИмяВерсияТип
    @item.Name@item.Version@item.Type - Изменить - Удалить -
    -
    - -@await Html.PartialAsync("DeleteDialog", new DeleteDialogViewModel -{ - Controller = "Configurations", - Action = "Delete" -}) +@using OnecMonitor.Server.ViewModels +@model OnecMonitor.Server.ViewModels.V8Files.V8FilesIndexViewModel + +@{ + ViewData["Title"] = "Файлы 1С"; +} + +

    Файлы 1С

    + + + +
    + + + + + + + + + + + @foreach (var item in Model.Items) + { + + + + + + + } + +
    ИмяВерсияТип
    @item.Name@item.Version@item.Type + Изменить + Удалить +
    +
    + +@await Html.PartialAsync("DeleteDialog", new DeleteDialogViewModel +{ + Controller = "V8Files", + Action = "Delete" +}) diff --git a/onecmonitor-server/appsettings.json b/onecmonitor-server/appsettings.json index c4510d3..2b5a603 100644 --- a/onecmonitor-server/appsettings.json +++ b/onecmonitor-server/appsettings.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Information", + "Default": "Trace", "Microsoft.Hosting.Lifetime": "Information" } }, diff --git a/onecmonitor-server/onecmonitor-server.csproj b/onecmonitor-server/onecmonitor-server.csproj index b4478e1..a25cdee 100644 --- a/onecmonitor-server/onecmonitor-server.csproj +++ b/onecmonitor-server/onecmonitor-server.csproj @@ -50,6 +50,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -78,6 +79,14 @@ <_ContentIncludedByDefault Remove="Views\MaintenanceTasks\Steps\LoadExtension.cshtml" /> <_ContentIncludedByDefault Remove="Views\MaintenanceTasks\Steps\LockConnections.cshtml" /> <_ContentIncludedByDefault Remove="Views\MaintenanceTasks\Steps\UpdateConfiguration.cshtml" /> + <_ContentIncludedByDefault Remove="Views\UpdateInfoBaseTasks\Steps\LockConnections.cshtml" /> + <_ContentIncludedByDefault Remove="Views\UpdateInfoBaseTasks\Steps\UpdateConfiguration.cshtml" /> + + + + + + diff --git a/onecmonitor-server/package.json b/onecmonitor-server/package.json index 2895ed4..b011021 100644 --- a/onecmonitor-server/package.json +++ b/onecmonitor-server/package.json @@ -13,7 +13,9 @@ "chart.js": "4.4.1", "signalr": "^2.4.3", "vis-timeline": "7.7.3", - "visjs-network": "4.25.0" + "visjs-network": "4.25.0", + "mermaid": "^11.4.1", + "uuid": "^11.0.5" }, "devDependencies": { "@types/ace": "^0.0.52", diff --git a/onecmonitor-server/tsconfig.json b/onecmonitor-server/tsconfig.json index 6a4b618..d3c5635 100644 --- a/onecmonitor-server/tsconfig.json +++ b/onecmonitor-server/tsconfig.json @@ -7,6 +7,11 @@ "allowJs": true, "skipLibCheck": true, "moduleResolution": "node", + "sourceRoot": "Scripts", + "mapRoot": "wwwroot", + "typeRoots": [ + "node_modules/@types" + ] }, "exclude": [ "node_modules", diff --git a/onecmonitor-server/webpack.config.js b/onecmonitor-server/webpack.config.js deleted file mode 100644 index 964a4aa..0000000 --- a/onecmonitor-server/webpack.config.js +++ /dev/null @@ -1,41 +0,0 @@ -const path = require('path'); -const webpack = require('webpack'); - - -module.exports = { - entry: { - script: [ - './Scripts/index.ts' - ] - }, - devtool: 'inline-source-map', - optimization: { - minimize: false, - usedExports: false - }, - module: { - rules: [ - { - test: /\.tsx?$/, - loader: 'ts-loader', - options: { - transpileOnly: true - } - }, - { - test: /\.css$/i, - use: ['style-loader', 'css-loader'] - }, - ], - }, - resolve: { - extensions: ['.tsx', '.ts', '.js'], - }, - output: { - path: path.resolve(__dirname, 'wwwroot'), - filename: 'dist/app.js', - library: { - - } - }, -}; \ No newline at end of file