mirror of
https://github.com/akpaevj/onecmonitor.git
synced 2025-03-03 14:42:18 +02:00
промежуточная
This commit is contained in:
parent
da06b000c1
commit
9f690f69a4
@ -1,55 +0,0 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
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<ElementAddress> Parse(byte[] buf)
|
||||
{
|
||||
const int elementSize = 4 + 4 + 4;
|
||||
var result = new List<ElementAddress>();
|
||||
|
||||
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}";
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
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; }
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace OneSTools.Common.Platform.Unpack;
|
||||
|
||||
public class File8Collection : IEnumerable<File8>
|
||||
{
|
||||
private readonly IReadOnlyList<File8?> _data;
|
||||
|
||||
public File8Collection(IEnumerable<File8?> data)
|
||||
{
|
||||
var fileList = new List<File8?>();
|
||||
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<File8> GetEnumerator()
|
||||
{
|
||||
return _data.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
namespace OneSTools.Common.Platform.Unpack;
|
||||
|
||||
public class File8FormatException : Exception;
|
@ -1,138 +0,0 @@
|
||||
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<File8> ReadFileList()
|
||||
{
|
||||
var containerHeader = ContainerHeader.Read(_reader);
|
||||
_storageVersion = (int)containerHeader.StorageVer;
|
||||
var elemsAddrBuf = BlockReader.ReadDataBlock(_reader);
|
||||
var addresses = ElementAddress.Parse(elemsAddrBuf);
|
||||
|
||||
var fileList = new List<File8>();
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Извлекает все файлы из контейнера.
|
||||
/// </summary>
|
||||
/// <param name="destDir">Каталог назначения.</param>
|
||||
/// <param name="recursiveUnpack">Если установлен в Истина, то все найденные вложенные восьмофайлы
|
||||
/// будут распакованы в отдельные подкаталоги. Необязательный.</param>
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait ContinueOnCapturedContext="false" />
|
||||
</Weavers>
|
@ -1,68 +1,65 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using OnecMonitor.Agent;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace OnecMonitor.Agent.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20230113112033_Init")]
|
||||
partial class Init
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.2");
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Agent.Models.TechLogSeance", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FinishDateTime")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StartDateTime")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Template")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("TechLogSeances");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Common.Models.AgentInstance", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("InstanceName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<double>("UtcOffset")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AgentInstance");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using OnecMonitor.Agent;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace OnecMonitor.Agent.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20250227185511_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.1");
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Agent.Models.AgentInstance", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("InstanceName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AgentInstance");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Agent.Models.TechLogSeance", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FinishDateTime")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StartDateTime")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Template")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("TechLogSeances");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -1,52 +1,51 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace OnecMonitor.Agent.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Init : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AgentInstance",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
InstanceName = table.Column<string>(type: "TEXT", nullable: false),
|
||||
UtcOffset = table.Column<double>(type: "REAL", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AgentInstance", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TechLogSeances",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
StartDateTime = table.Column<string>(type: "TEXT", nullable: false),
|
||||
FinishDateTime = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Template = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Status = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TechLogSeances", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AgentInstance");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TechLogSeances");
|
||||
}
|
||||
}
|
||||
}
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace OnecMonitor.Agent.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AgentInstance",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
InstanceName = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AgentInstance", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TechLogSeances",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
StartDateTime = table.Column<string>(type: "TEXT", nullable: false),
|
||||
FinishDateTime = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Template = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Status = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TechLogSeances", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AgentInstance");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TechLogSeances");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,65 +1,62 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using OnecMonitor.Agent;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace OnecMonitor.Agent.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
partial class AppDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.2");
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Agent.Models.TechLogSeance", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FinishDateTime")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StartDateTime")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Template")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("TechLogSeances");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Common.Models.AgentInstance", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("InstanceName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<double>("UtcOffset")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AgentInstance");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using OnecMonitor.Agent;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace OnecMonitor.Agent.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
partial class AppDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.1");
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Agent.Models.AgentInstance", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("InstanceName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AgentInstance");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Agent.Models.TechLogSeance", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FinishDateTime")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StartDateTime")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Template")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("TechLogSeances");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
|
7
onecmonitor-agent/Models/AgentInstance.cs
Normal file
7
onecmonitor-agent/Models/AgentInstance.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace OnecMonitor.Agent.Models;
|
||||
|
||||
public class AgentInstance
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.Empty;
|
||||
public string InstanceName { get; set; } = string.Empty;
|
||||
}
|
@ -2,8 +2,10 @@ using System.Reflection;
|
||||
using Grpc.Net.Client;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OnecMonitor.Agent;
|
||||
using OnecMonitor.Agent.Models;
|
||||
using OnecMonitor.Agent.Services;
|
||||
using OnecMonitor.Agent.Services.InfoBases;
|
||||
using OnecMonitor.Agent.Services.MaintenanceTasks;
|
||||
using OnecMonitor.Agent.Services.TechLog;
|
||||
using OnecMonitor.Common.Storage;
|
||||
using OnecMonitor.Common.TechLog;
|
||||
@ -19,30 +21,65 @@ var host = Host.CreateDefaultBuilder(args)
|
||||
services.AddSingleton<RasHolder>();
|
||||
services.AddDbContext<AppDbContext>();
|
||||
|
||||
// Commands watcher connection
|
||||
services.AddSingleton<OnecMonitorConnection>();
|
||||
services.AddTransient<OnecMonitorConnection>();
|
||||
|
||||
services.AddSingleton<TechLogFolderWatcher>();
|
||||
services.AddSingleton<TechLogExporter>();
|
||||
services.AddHostedService<TechLogSeancesWatcher>();
|
||||
|
||||
services.AddSingleton<MaintenanceTaskExecutorQueue>();
|
||||
services.AddHostedService<MaintenanceTaskExecutor>();
|
||||
|
||||
services.AddSingleton<InfoBasesUpdateTasksQueue>();
|
||||
services.AddHostedService<InfoBasesUpdater>();
|
||||
|
||||
services.AddHostedService<CommandsWatcher>();
|
||||
services.AddSingleton<CommandsWatcher>();
|
||||
})
|
||||
.Build();
|
||||
|
||||
var appLifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();
|
||||
|
||||
await using var scope = host.Services.CreateAsyncScope();
|
||||
await using var appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
await appDbContext.Database.MigrateAsync();
|
||||
|
||||
var appLifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();
|
||||
var techLogeExporter = host.Services.GetRequiredService<TechLogExporter>();
|
||||
// update agent instance info
|
||||
var configuration = host.Services.GetRequiredService<IConfiguration>();
|
||||
|
||||
var agentInstance = appDbContext.AgentInstance.FirstOrDefault();
|
||||
|
||||
var instanceName = configuration.GetValue("Agent:InstanceName", Environment.MachineName);
|
||||
if (string.IsNullOrEmpty(instanceName))
|
||||
instanceName = Environment.MachineName;
|
||||
|
||||
if (agentInstance == null)
|
||||
{
|
||||
agentInstance = new AgentInstance
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
InstanceName = instanceName
|
||||
};
|
||||
|
||||
appDbContext.AgentInstance.Add(agentInstance);
|
||||
appDbContext.SaveChanges();
|
||||
}
|
||||
else if (agentInstance.InstanceName != instanceName)
|
||||
{
|
||||
agentInstance.InstanceName = instanceName;
|
||||
|
||||
appDbContext.AgentInstance.Update(agentInstance);
|
||||
appDbContext.SaveChanges();
|
||||
}
|
||||
|
||||
|
||||
// another init actions
|
||||
var techLogExporter = host.Services.GetRequiredService<TechLogExporter>();
|
||||
appLifetime.ApplicationStopping.Register(() =>
|
||||
{
|
||||
techLogeExporter.Stop();
|
||||
techLogeExporter.Dispose();
|
||||
techLogExporter.Stop();
|
||||
techLogExporter.Dispose();
|
||||
});
|
||||
|
||||
await host.Services.GetRequiredService<CommandsWatcher>().Start(appLifetime.ApplicationStopping);
|
||||
|
||||
host.Run();
|
||||
|
@ -1,28 +1,34 @@
|
||||
using MessagePack;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OnecMonitor.Agent.Services.InfoBases;
|
||||
using OnecMonitor.Agent.Services.MaintenanceTasks;
|
||||
using OnecMonitor.Agent.Services.TechLog;
|
||||
using OnecMonitor.Common.DTO;
|
||||
using OnecMonitor.Common.DTO.MaintenanceTasks;
|
||||
using OneSTools.Common.Platform;
|
||||
using OneSTools.Common.Platform.RemoteAdministration;
|
||||
using OneSTools.Common.Platform.Services;
|
||||
|
||||
namespace OnecMonitor.Agent.Services
|
||||
{
|
||||
internal class CommandsWatcher : BackgroundService
|
||||
internal class CommandsWatcher
|
||||
{
|
||||
private readonly OnecMonitorConnection _server;
|
||||
private readonly AppDbContext _appDbContext;
|
||||
private readonly InfoBasesUpdateTasksQueue _updateTasksQueue;
|
||||
private readonly MaintenanceTaskExecutorQueue _maintenanceTasksQueue;
|
||||
private readonly RasHolder _rasHolder;
|
||||
private readonly TechLogExporter _techLogExporter;
|
||||
private readonly IHostApplicationLifetime _applicationLifetime;
|
||||
private readonly ILogger<CommandsWatcher> _logger;
|
||||
|
||||
public CommandsWatcher(
|
||||
IServiceProvider serviceProvider,
|
||||
InfoBasesUpdateTasksQueue updateTasksQueue,
|
||||
MaintenanceTaskExecutorQueue maintenanceTasksQueue,
|
||||
TechLogExporter techLogExporter,
|
||||
RasHolder rasHolder,
|
||||
RasHolder rasHolder,
|
||||
IHostApplicationLifetime appLifetime,
|
||||
ILogger<CommandsWatcher> logger)
|
||||
{
|
||||
var scope = serviceProvider.CreateAsyncScope();
|
||||
@ -31,65 +37,74 @@ namespace OnecMonitor.Agent.Services
|
||||
_appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
_techLogExporter = techLogExporter;
|
||||
_updateTasksQueue = updateTasksQueue;
|
||||
_maintenanceTasksQueue = maintenanceTasksQueue;
|
||||
_applicationLifetime = appLifetime;
|
||||
_logger = logger;
|
||||
|
||||
_server.MessageReceived += MessageReceived;
|
||||
_applicationLifetime.ApplicationStopping.Register(() =>
|
||||
{
|
||||
_server.MessageReceived -= MessageReceived;
|
||||
});
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
private async void MessageReceived(object? sender, Message message)
|
||||
{
|
||||
_logger.LogTrace("Start watching commands");
|
||||
|
||||
await _server.Send(MessageType.SubscribingForCommands, stoppingToken);
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
try
|
||||
{
|
||||
try
|
||||
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
|
||||
switch (message.Header.Type)
|
||||
{
|
||||
var message = await _server.ReadMessage(stoppingToken);
|
||||
|
||||
await UpdateSettings(stoppingToken);
|
||||
|
||||
try
|
||||
{
|
||||
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
|
||||
switch (message.Header.Type)
|
||||
{
|
||||
case MessageType.UpdateTechLogSeancesRequest:
|
||||
await UpdateTechLogSeancesByRequest(message, stoppingToken);
|
||||
break;
|
||||
case MessageType.InstalledPlatformsRequest:
|
||||
await SendInstalledPlatforms(message, stoppingToken);
|
||||
break;
|
||||
case MessageType.ClustersRequest:
|
||||
await SendV8Clusters(message, stoppingToken);
|
||||
break;
|
||||
case MessageType.InfoBasesRequest:
|
||||
await SendV8InfoBases(message, stoppingToken);
|
||||
break;
|
||||
case MessageType.RagentServicesRequest:
|
||||
await SendRagentServices(message, stoppingToken);
|
||||
break;
|
||||
case MessageType.RasServicesRequest:
|
||||
await SendRasServices(message, stoppingToken);
|
||||
break;
|
||||
case MessageType.UpdateInfoBasesRequest:
|
||||
await HandleUpdateInfoBasesRequest(message, stoppingToken);
|
||||
break;
|
||||
case MessageType.UpdateSettingsRequest:
|
||||
await HandleUpdateSettingsRequest(message, stoppingToken);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Получено неожиданное сообщение: {message.Header.Type}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) {}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogTrace(ex, "Ошибка обработки сообщения");
|
||||
await _server.SendError(message, ex.Message, stoppingToken);
|
||||
}
|
||||
case MessageType.UpdateTechLogSeancesRequest:
|
||||
await UpdateTechLogSeancesByRequest(message, _applicationLifetime.ApplicationStopping);
|
||||
break;
|
||||
case MessageType.InstalledPlatformsRequest:
|
||||
await SendInstalledPlatforms(message, _applicationLifetime.ApplicationStopping);
|
||||
break;
|
||||
case MessageType.ClustersRequest:
|
||||
await SendV8Clusters(message, _applicationLifetime.ApplicationStopping);
|
||||
break;
|
||||
case MessageType.InfoBasesRequest:
|
||||
await SendV8InfoBases(message, _applicationLifetime.ApplicationStopping);
|
||||
break;
|
||||
case MessageType.RagentServicesRequest:
|
||||
await SendRagentServices(message, _applicationLifetime.ApplicationStopping);
|
||||
break;
|
||||
case MessageType.RasServicesRequest:
|
||||
await SendRasServices(message, _applicationLifetime.ApplicationStopping);
|
||||
break;
|
||||
case MessageType.UpdateInfoBasesRequest:
|
||||
await HandleUpdateInfoBasesRequest(message, _applicationLifetime.ApplicationStopping);
|
||||
break;
|
||||
case MessageType.UpdateSettingsRequest:
|
||||
await HandleUpdateSettingsRequest(message, _applicationLifetime.ApplicationStopping);
|
||||
break;
|
||||
case MessageType.MaintenanceTask:
|
||||
await HandleMaintenanceTask(message, _applicationLifetime.ApplicationStopping);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Получено неожиданное сообщение: {message.Header.Type}");
|
||||
}
|
||||
catch (OperationCanceledException) {}
|
||||
}
|
||||
catch (OperationCanceledException) {}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogTrace(ex, "Ошибка обработки сообщения");
|
||||
await _server.SendError(message, ex.Message, _applicationLifetime.ApplicationStopping);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleMaintenanceTask(Message message, CancellationToken cancellationToken)
|
||||
{
|
||||
var task = MessagePackSerializer.Deserialize<MaintenanceTaskDto>(message.Data, cancellationToken: cancellationToken);
|
||||
await _maintenanceTasksQueue.QueueAsync(task, cancellationToken);
|
||||
|
||||
await _server.SendOk(message, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task Start(CancellationToken token)
|
||||
{
|
||||
await _server.Start(true);
|
||||
}
|
||||
|
||||
private async Task SendInstalledPlatforms(Message message, CancellationToken cancellationToken)
|
||||
@ -106,8 +121,9 @@ namespace OnecMonitor.Agent.Services
|
||||
|
||||
private async Task HandleUpdateSettingsRequest(Message message, CancellationToken cancellationToken)
|
||||
{
|
||||
await _server.SendOk(message, cancellationToken);
|
||||
await UpdateSettings(cancellationToken);
|
||||
|
||||
await _server.SendOk(message, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task UpdateSettings(CancellationToken cancellationToken)
|
||||
|
@ -12,12 +12,10 @@ public sealed class InfoBasesUpdater : BackgroundService
|
||||
private readonly InfoBasesUpdateTasksQueue _queue;
|
||||
|
||||
private readonly AsyncServiceScope _scope;
|
||||
private readonly OnecMonitorConnection _server;
|
||||
private readonly OnecMonitorConnection _serverConnection;
|
||||
private readonly IHostApplicationLifetime _applicationLifetime;
|
||||
private readonly RasHolder _rasHolder;
|
||||
private readonly ILogger<InfoBasesUpdater> _logger;
|
||||
private bool _disposed;
|
||||
private readonly object _racLocker = new();
|
||||
|
||||
public InfoBasesUpdater(
|
||||
IServiceProvider serviceProvider,
|
||||
@ -28,7 +26,7 @@ public sealed class InfoBasesUpdater : BackgroundService
|
||||
{
|
||||
_scope = serviceProvider.CreateAsyncScope();
|
||||
_queue = tasksQueue;
|
||||
_server = _scope.ServiceProvider.GetRequiredService<OnecMonitorConnection>();
|
||||
_serverConnection = _scope.ServiceProvider.GetRequiredService<OnecMonitorConnection>();
|
||||
_applicationLifetime = applicationLifetime;
|
||||
_rasHolder = rasHolder;
|
||||
_logger = logger;
|
||||
@ -36,6 +34,8 @@ public sealed class InfoBasesUpdater : BackgroundService
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await _serverConnection.Start();
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
await _queue.DequeueAsync(stoppingToken);
|
||||
@ -58,7 +58,7 @@ public sealed class InfoBasesUpdater : BackgroundService
|
||||
const string accessCode = "12345";
|
||||
const string message = "Технические работы";
|
||||
|
||||
var task = await _server.Get<UpdateInfoBaseTaskDto>(
|
||||
var task = await _serverConnection.Get<UpdateInfoBaseTaskDto>(
|
||||
MessageType.UpdateInfoBasesTaskRequest,
|
||||
MessageType.UpdateInfoBasesTask,
|
||||
_applicationLifetime.ApplicationStopping);
|
||||
@ -69,7 +69,7 @@ public sealed class InfoBasesUpdater : BackgroundService
|
||||
var configurationsPaths = new Dictionary<string, string>();
|
||||
task.Files.ForEach(i =>
|
||||
{
|
||||
var path = Path.Join(Path.GetTempPath(), $"{i.Id}.cfu") ;
|
||||
var path = Path.Join(Path.GetTempPath(), $"{i.Id}{i.FileExtension}") ;
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
@ -108,35 +108,31 @@ public sealed class InfoBasesUpdater : BackgroundService
|
||||
|
||||
await AddLogItemAndSend(task, infoBase, log, "Блокировка соединений и регламентных заданий", cancellationToken);
|
||||
|
||||
lock (_racLocker)
|
||||
rac.BlockConnections(
|
||||
infoBase.Cluster.Id,
|
||||
infoBase.InfoBaseInternalId,
|
||||
infoBase.Credentials.User,
|
||||
infoBase.Credentials.Password,
|
||||
accessCode,
|
||||
message);
|
||||
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 =>
|
||||
var sessions = rac.GetInfoBaseSessions(infoBase.Cluster.Id, infoBase.InfoBaseInternalId);
|
||||
sessions
|
||||
.Where(c => !c.AppId.Contains("RAS", StringComparison.CurrentCultureIgnoreCase))
|
||||
.ToList()
|
||||
.ForEach(s =>
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
rac.TerminateSession(infoBase.Cluster.Id, s.Id);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Игнорируем, т.к. сеанс уже мог быть закрыт, мог быть повисшим и т.п.
|
||||
}
|
||||
});
|
||||
}
|
||||
rac.TerminateSession(infoBase.Cluster.Id, s.Id);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Игнорируем, т.к. сеанс уже мог быть закрыт, мог быть повисшим и т.п.
|
||||
}
|
||||
});
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
@ -189,7 +185,7 @@ public sealed class InfoBasesUpdater : BackgroundService
|
||||
await AddLogItemAndSend(task, infoBase, log, loadExtBatch.OutFileContent, cancellationToken);
|
||||
}
|
||||
|
||||
var needAcceptLegalUsing = config != null;
|
||||
var needAcceptLegalUsing = config is { IsConfiguration: true };
|
||||
if (needAcceptLegalUsing)
|
||||
{
|
||||
await AddLogItemAndSend(task, infoBase, log, "Подтверждение легальности получения и запуск обработчиков обновления", cancellationToken);
|
||||
@ -206,12 +202,11 @@ public sealed class InfoBasesUpdater : BackgroundService
|
||||
|
||||
await AddLogItemAndSend(task, infoBase, log, "Разблокировка соединений и регламентных заданий", cancellationToken);
|
||||
|
||||
lock (_racLocker)
|
||||
rac.UnblockConnections(
|
||||
infoBase.Cluster.Id,
|
||||
infoBase.InfoBaseInternalId,
|
||||
infoBase.Credentials.User,
|
||||
infoBase.Credentials.Password);
|
||||
rac.UnblockConnections(
|
||||
infoBase.Cluster.Id,
|
||||
infoBase.InfoBaseInternalId,
|
||||
infoBase.Credentials.User,
|
||||
infoBase.Credentials.Password);
|
||||
|
||||
await AddLogItemAndSend(task, infoBase, log, "Обновление завершено", _applicationLifetime.ApplicationStopping, false, true);
|
||||
}
|
||||
@ -256,30 +251,14 @@ public sealed class InfoBasesUpdater : BackgroundService
|
||||
}
|
||||
|
||||
private async Task SendLog(List<UpdateInfoBaseTaskLogItemDto> log, CancellationToken cancellationToken)
|
||||
=> await _server.Send(MessageType.UpdateInfoBaseTaskLog, log, cancellationToken);
|
||||
=> await _serverConnection.Send(MessageType.UpdateInfoBaseTaskLog, log, cancellationToken);
|
||||
|
||||
private static string GetExternalDataProcessorPath(string fileName)
|
||||
=> Path.Join(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Asserts", fileName);
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_scope.Dispose();
|
||||
_server.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public override Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_scope.Dispose();
|
||||
_server.Dispose();
|
||||
|
||||
return base.StopAsync(cancellationToken);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
using System.Collections.Concurrent;
|
||||
using OnecMonitor.Common.DTO;
|
||||
using OnecMonitor.Common.DTO.MaintenanceTasks;
|
||||
using OneSTools.Common.Designer.Batch;
|
||||
using OneSTools.Common.Platform;
|
||||
using OneSTools.Common.Platform.RemoteAdministration;
|
||||
|
||||
namespace OnecMonitor.Agent.Services.MaintenanceTasks;
|
||||
|
||||
public class MaintenanceStepNodeContext
|
||||
{
|
||||
public InfoBaseDto InfoBase { get; set; } = null!;
|
||||
public MaintenanceStepNodeDto Node { get; set; } = null!;
|
||||
public string AccessCode { get; set; } = string.Empty;
|
||||
public ConcurrentDictionary<Guid, string> V8Files { get; set; } = [];
|
||||
public List<MaintenanceStepNodeLogItemDto> Log { get; set; } = [];
|
||||
public Rac Rac { get; set; } = null!;
|
||||
public V8Platform Platform { get; set; } = null!;
|
||||
|
||||
public OnecV8BatchMode GetBatchDesigner()
|
||||
=> new(Platform, $"{InfoBase.Cluster.Host}:{InfoBase.Cluster.Port}", InfoBase.InfoBaseName);
|
||||
|
||||
public OnecV8BatchMode GetBatchEnterprise()
|
||||
=> new(Platform, $"{InfoBase.Cluster.Host}:{InfoBase.Cluster.Port}", InfoBase.InfoBaseName, false);
|
||||
}
|
@ -0,0 +1,296 @@
|
||||
using System.Collections.Concurrent;
|
||||
using OnecMonitor.Agent.Services.InfoBases;
|
||||
using OnecMonitor.Common.DTO;
|
||||
using OnecMonitor.Common.DTO.MaintenanceTasks;
|
||||
using OnecMonitor.Common.Extensions;
|
||||
using OnecMonitor.Common.Models.MaintenanceTasks;
|
||||
using OneSTools.Common.Designer.Batch;
|
||||
using OneSTools.Common.Platform.RemoteAdministration;
|
||||
using OneSTools.Common.Platform.Services;
|
||||
|
||||
namespace OnecMonitor.Agent.Services.MaintenanceTasks;
|
||||
|
||||
public class MaintenanceTaskExecutor : BackgroundService
|
||||
{
|
||||
private readonly MaintenanceTaskExecutorQueue _queue;
|
||||
|
||||
private readonly AsyncServiceScope _scope;
|
||||
private readonly OnecMonitorConnection _serverConnection;
|
||||
private readonly IHostApplicationLifetime _applicationLifetime;
|
||||
private readonly RasHolder _rasHolder;
|
||||
private readonly ILogger<MaintenanceTaskExecutor> _logger;
|
||||
|
||||
public MaintenanceTaskExecutor(
|
||||
IServiceProvider serviceProvider,
|
||||
MaintenanceTaskExecutorQueue tasksQueue,
|
||||
RasHolder rasHolder,
|
||||
IHostApplicationLifetime applicationLifetime,
|
||||
ILogger<MaintenanceTaskExecutor> logger)
|
||||
{
|
||||
_scope = serviceProvider.CreateAsyncScope();
|
||||
_queue = tasksQueue;
|
||||
_serverConnection = _scope.ServiceProvider.GetRequiredService<OnecMonitorConnection>();
|
||||
_applicationLifetime = applicationLifetime;
|
||||
_rasHolder = rasHolder;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await _serverConnection.Start();
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
var task = await _queue.DequeueAsync(stoppingToken);
|
||||
|
||||
try
|
||||
{
|
||||
await StartMaintenanceTask(task, stoppingToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, $"Ошибка обработки задания обслуживания: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartMaintenanceTask(MaintenanceTaskDto task, CancellationToken cancellationToken)
|
||||
{
|
||||
var v8Files = await SaveTaskV8Files(task, cancellationToken);
|
||||
|
||||
await Parallel.ForEachAsync(task.InfoBases, cancellationToken, async (infoBase, stoppingToken) =>
|
||||
{
|
||||
var log = new List<MaintenanceStepNodeLogItemDto>();
|
||||
|
||||
var context = new MaintenanceStepNodeContext
|
||||
{
|
||||
InfoBase = infoBase,
|
||||
Log = log,
|
||||
V8Files = v8Files,
|
||||
Node = task.RootNode
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var ragent = V8Services.GetActiveRagentForClusterPort(infoBase.Cluster.Port);
|
||||
var ras = _rasHolder.GetActiveRasForRagent(ragent);
|
||||
|
||||
context.Rac = Rac.GetRacForRasService(ras);
|
||||
context.Platform = ragent.Platform;
|
||||
|
||||
if (!context.Platform.HasOnecV8)
|
||||
throw new Exception("Для платформы агента не установлен конфигуратор");
|
||||
|
||||
var currentNode = task.RootNode;
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested && currentNode != null)
|
||||
{
|
||||
context.Node = currentNode;
|
||||
|
||||
if (currentNode.Kind == MaintenanceStepNodeKind.TryCatch)
|
||||
{
|
||||
try
|
||||
{
|
||||
HandleTaskStepNode(context);
|
||||
currentNode = currentNode.LeftNode;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
AddLogItem(context, e.ToString(), true);
|
||||
currentNode = currentNode!.RightNode;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleTaskStepNode(context);
|
||||
currentNode = currentNode.LeftNode;
|
||||
}
|
||||
}
|
||||
|
||||
AddLogItem(context, "Завершено", false, true);
|
||||
await SendLog(log, stoppingToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
AddLogItem(context, e.ToString(), true, true);
|
||||
await SendLog(log, stoppingToken);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void HandleTaskStepNode(MaintenanceStepNodeContext context)
|
||||
{
|
||||
AddLogItem(context, $"Обработка шага \"{context.Node.Step.Kind.GetDisplay()}\"");
|
||||
|
||||
switch (context.Node.Step.Kind)
|
||||
{
|
||||
case MaintenanceStepKind.LockConnections:
|
||||
LockConnections(context);
|
||||
break;
|
||||
case MaintenanceStepKind.CloseConnections:
|
||||
CloseConnections(context);
|
||||
break;
|
||||
case MaintenanceStepKind.UnlockConnections:
|
||||
UnlockConnections(context);
|
||||
break;
|
||||
case MaintenanceStepKind.LoadExtension:
|
||||
LoadExtension(context);
|
||||
break;
|
||||
case MaintenanceStepKind.UpdateConfiguration:
|
||||
UpdateConfiguration(context);
|
||||
break;
|
||||
case MaintenanceStepKind.LoadConfiguration:
|
||||
LoadConfiguration(context);
|
||||
break;
|
||||
case MaintenanceStepKind.StartExternalDataProcessor:
|
||||
StartExternalDataProcessor(context);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Неизвестный тип шага \"{context.Node.Step.Kind.GetDisplay()}\"");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddLogItem(MaintenanceStepNodeContext context, string message, bool isError = false, bool isFinish = false)
|
||||
{
|
||||
context.Log.Add(new MaintenanceStepNodeLogItemDto
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Message = message,
|
||||
IsError = isError,
|
||||
IsFinish = isFinish,
|
||||
TimeStamp = DateTime.Now,
|
||||
InfoBaseId = context.InfoBase.Id,
|
||||
StepNodeId = context.Node.Id
|
||||
});
|
||||
}
|
||||
|
||||
private async Task SendLog(List<MaintenanceStepNodeLogItemDto> log, CancellationToken cancellationToken)
|
||||
=> await _serverConnection.Send(MessageType.MaintenanceStepNodeLog, log, cancellationToken);
|
||||
|
||||
private static async Task<ConcurrentDictionary<Guid, string>> SaveTaskV8Files(MaintenanceTaskDto task,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var files = new ConcurrentDictionary<Guid, string>();
|
||||
await SaveStepNodeV8File(task.RootNode, files, cancellationToken);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private static async Task SaveStepNodeV8File(MaintenanceStepNodeDto node, ConcurrentDictionary<Guid, string> files, CancellationToken cancellationToken)
|
||||
{
|
||||
if (node.Step.File != null && !files.ContainsKey(node.Step.File.Id))
|
||||
{
|
||||
var path = Path.Join(Path.GetTempPath(), $"{node.Step.File.Id}{node.Step.File.FileExtension}") ;
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
await using var file = File.Create(path);
|
||||
file.Write(node.Step.File.Data);
|
||||
file.Close();
|
||||
}
|
||||
|
||||
files.TryAdd(node.Step.File.Id, path);
|
||||
node.Step.File.Data = null!;
|
||||
}
|
||||
|
||||
if (node.LeftNode != null)
|
||||
await SaveStepNodeV8File(node.LeftNode, files, cancellationToken);
|
||||
|
||||
if (node.RightNode != null)
|
||||
await SaveStepNodeV8File(node.RightNode, files, cancellationToken);
|
||||
}
|
||||
|
||||
private static void LockConnections(MaintenanceStepNodeContext context)
|
||||
{
|
||||
context.Rac.BlockConnections(
|
||||
context.InfoBase.Cluster.Id,
|
||||
context.InfoBase.InfoBaseInternalId,
|
||||
context.InfoBase.Credentials.User,
|
||||
context.InfoBase.Credentials.Password,
|
||||
context.Node.Step.AccessCode,
|
||||
context.Node.Step.Message);
|
||||
|
||||
context.AccessCode = context.Node.Step.AccessCode;
|
||||
}
|
||||
|
||||
private static void CloseConnections(MaintenanceStepNodeContext context)
|
||||
{
|
||||
var sessions = context.Rac.GetInfoBaseSessions(context.InfoBase.Cluster.Id, context.InfoBase.InfoBaseInternalId);
|
||||
sessions
|
||||
.Where(c => !c.AppId.Contains("RAS", StringComparison.CurrentCultureIgnoreCase))
|
||||
.ToList()
|
||||
.ForEach(s =>
|
||||
{
|
||||
try
|
||||
{
|
||||
context.Rac.TerminateSession(context.InfoBase.Cluster.Id, s.Id);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Игнорируем, т.к. сеанс уже мог быть закрыт, мог быть повисшим и т.п.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void UnlockConnections(MaintenanceStepNodeContext context)
|
||||
{
|
||||
context.Rac.UnblockConnections(
|
||||
context.InfoBase.Cluster.Id,
|
||||
context.InfoBase.InfoBaseInternalId,
|
||||
context.InfoBase.Credentials.User,
|
||||
context.InfoBase.Credentials.Password);
|
||||
}
|
||||
|
||||
private static void LoadExtension(MaintenanceStepNodeContext context)
|
||||
{
|
||||
var filePath = context.V8Files[context.Node.Step.File!.Id];
|
||||
|
||||
using var batch = context.GetBatchDesigner();
|
||||
batch.LoadExtension(
|
||||
context.Node.Step.File.Name,
|
||||
filePath,
|
||||
context.InfoBase.Credentials.User,
|
||||
context.InfoBase.Credentials.Password,
|
||||
context.AccessCode,
|
||||
true);
|
||||
}
|
||||
|
||||
private static void LoadConfiguration(MaintenanceStepNodeContext context)
|
||||
{
|
||||
var filePath = context.V8Files[context.Node.Step.File!.Id];
|
||||
|
||||
using var batch = context.GetBatchDesigner();
|
||||
batch.LoadConfiguration(
|
||||
filePath,
|
||||
context.InfoBase.Credentials.User,
|
||||
context.InfoBase.Credentials.Password,
|
||||
context.AccessCode,
|
||||
true);
|
||||
}
|
||||
|
||||
private static void UpdateConfiguration(MaintenanceStepNodeContext context)
|
||||
{
|
||||
var filePath = context.V8Files[context.Node.Step.File!.Id];
|
||||
|
||||
using var batch = context.GetBatchDesigner();
|
||||
batch.UpdateConfiguration(
|
||||
filePath,
|
||||
context.InfoBase.Credentials.User,
|
||||
context.InfoBase.Credentials.Password,
|
||||
context.AccessCode,
|
||||
true);
|
||||
}
|
||||
|
||||
private static void StartExternalDataProcessor(MaintenanceStepNodeContext context)
|
||||
{
|
||||
var filePath = context.V8Files[context.Node.Step.File!.Id];
|
||||
|
||||
var batch = context.GetBatchEnterprise();
|
||||
batch.ExecuteExternalDataProcessor(
|
||||
filePath,
|
||||
context.InfoBase.Credentials.User,
|
||||
context.InfoBase.Credentials.Password,
|
||||
context.AccessCode,
|
||||
true);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
using System.Threading.Channels;
|
||||
using OnecMonitor.Common.DTO;
|
||||
using OnecMonitor.Common.DTO.MaintenanceTasks;
|
||||
|
||||
namespace OnecMonitor.Agent.Services.MaintenanceTasks;
|
||||
|
||||
public class MaintenanceTaskExecutorQueue
|
||||
{
|
||||
private readonly Channel<MaintenanceTaskDto> _updateRequestsChannel = Channel.CreateUnbounded<MaintenanceTaskDto>();
|
||||
|
||||
public async Task QueueAsync(MaintenanceTaskDto task, CancellationToken cancellationToken)
|
||||
=> await _updateRequestsChannel.Writer.WriteAsync(task, cancellationToken);
|
||||
|
||||
public async Task<MaintenanceTaskDto> DequeueAsync(CancellationToken cancellationToken)
|
||||
=> await _updateRequestsChannel.Reader.ReadAsync(cancellationToken);
|
||||
}
|
@ -3,56 +3,48 @@ using OnecMonitor.Common.DTO;
|
||||
using MessagePack;
|
||||
using System.Net.Sockets;
|
||||
using System.Net;
|
||||
using OnecMonitor.Agent.Models;
|
||||
using OneSTools.Common.Platform;
|
||||
|
||||
namespace OnecMonitor.Agent.Services
|
||||
{
|
||||
public class OnecMonitorConnection : ServerConnection
|
||||
{
|
||||
public OnecMonitorConnection(IServiceProvider serviceProvider, IHostApplicationLifetime hostApplicationLifetime)
|
||||
: base(serviceProvider.GetRequiredService<ILogger<OnecMonitorConnection>>())
|
||||
private readonly string _host;
|
||||
private readonly int _port;
|
||||
private readonly AgentInstance _agent;
|
||||
private readonly IHostApplicationLifetime _hostApplicationLifetime;
|
||||
|
||||
public OnecMonitorConnection(
|
||||
IServiceProvider serviceProvider,
|
||||
IConfiguration configuration,
|
||||
IHostApplicationLifetime hostApplicationLifetime,
|
||||
ILogger<OnecMonitorConnection> logger) : base(logger)
|
||||
{
|
||||
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
|
||||
_hostApplicationLifetime = hostApplicationLifetime;
|
||||
|
||||
using var scope = serviceProvider.CreateAsyncScope();
|
||||
using var appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
var agentInstance = appDbContext.AgentInstance.FirstOrDefault();
|
||||
_agent = appDbContext.AgentInstance.FirstOrDefault()!;
|
||||
|
||||
var instanceName = configuration.GetValue("Agent:InstanceName", Environment.MachineName);
|
||||
if (string.IsNullOrEmpty(instanceName))
|
||||
instanceName = Environment.MachineName;
|
||||
_host = configuration.GetValue("OnecMonitor:Host", "0.0.0.0");
|
||||
_port = configuration.GetValue("OnecMonitor:Port", 7001);
|
||||
}
|
||||
|
||||
if (agentInstance == null)
|
||||
public async Task Start(bool mainConnection = false)
|
||||
{
|
||||
await Start(_host, _port, async () =>
|
||||
{
|
||||
agentInstance = new AgentInstance
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
InstanceName = instanceName,
|
||||
UtcOffset = TimeZoneInfo.Local.GetUtcOffset(DateTime.UtcNow).TotalSeconds
|
||||
};
|
||||
|
||||
appDbContext.AgentInstance.Add(agentInstance);
|
||||
appDbContext.SaveChanges();
|
||||
}
|
||||
else if (agentInstance.InstanceName != instanceName)
|
||||
{
|
||||
agentInstance.InstanceName = instanceName;
|
||||
|
||||
appDbContext.AgentInstance.Update(agentInstance);
|
||||
appDbContext.SaveChanges();
|
||||
}
|
||||
|
||||
Connected += async (_, _) =>
|
||||
{
|
||||
await WriteMessageToStream(MessageType.AgentInfo, agentInstance, hostApplicationLifetime.ApplicationStopping);
|
||||
};
|
||||
|
||||
var host = configuration.GetValue("OnecMonitor:Host", "0.0.0.0");
|
||||
var port = configuration.GetValue("OnecMonitor:Port", 7001);
|
||||
|
||||
var logger = serviceProvider.GetRequiredService<ILogger<OnecMonitorConnection>>();
|
||||
|
||||
Start(host, port, logger, hostApplicationLifetime.ApplicationStopping);
|
||||
await WriteMessageToStream(MessageType.AgentInfo,
|
||||
new AgentInstanceDto
|
||||
{
|
||||
Id = _agent.Id,
|
||||
InstanceName = _agent.InstanceName,
|
||||
MainConnection = mainConnection,
|
||||
UtcOffset = TimeZoneInfo.Local.GetUtcOffset(DateTime.UtcNow).TotalSeconds
|
||||
},
|
||||
_hostApplicationLifetime.ApplicationStopping);
|
||||
}, _hostApplicationLifetime.ApplicationStopping);
|
||||
}
|
||||
}
|
||||
}
|
@ -50,6 +50,8 @@ namespace OnecMonitor.Agent.Services.TechLog
|
||||
{
|
||||
_techLogWatcher.Stop();
|
||||
});
|
||||
|
||||
_onecMonitorConnection.Start();
|
||||
|
||||
try
|
||||
{
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -20,11 +20,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwait.Fody" Version="3.3.2" PrivateAssets="All" />
|
||||
<PackageReference Include="Fody" Version="6.9.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MessagePack" Version="3.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.1">
|
||||
@ -45,6 +40,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Asserts\" />
|
||||
<Folder Include="Migrations\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -8,12 +8,14 @@ using System.Threading.Tasks;
|
||||
namespace OnecMonitor.Common.DTO;
|
||||
|
||||
[MessagePackObject]
|
||||
public class AgentInstance
|
||||
public class AgentInstanceDto
|
||||
{
|
||||
[Key(0)]
|
||||
public Guid Id { get; set; } = Guid.Empty;
|
||||
[Key(1)]
|
||||
public string InstanceName { get; set; } = string.Empty;
|
||||
[Key(2)]
|
||||
[Key(2)]
|
||||
public bool MainConnection { get; set; }
|
||||
[Key(3)]
|
||||
public double UtcOffset { get; set; } = 0;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using MessagePack;
|
||||
using OnecMonitor.Common.Models.MaintenanceTasks;
|
||||
|
||||
namespace OnecMonitor.Common.DTO.MaintenanceTasks;
|
||||
|
||||
[MessagePackObject]
|
||||
public class MaintenanceStepDto
|
||||
{
|
||||
[Key(0)]
|
||||
public Guid Id { get; set; } = Guid.Empty;
|
||||
[Key(1)]
|
||||
public MaintenanceStepKind Kind { get; set; }
|
||||
[Key(2)]
|
||||
public string AccessCode { get; set; } = string.Empty;
|
||||
[Key(3)]
|
||||
public string Message { get; set; } = string.Empty;
|
||||
[Key(4)]
|
||||
public V8FileDto? File { get; set; }
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using MessagePack;
|
||||
using OnecMonitor.Common.Models.MaintenanceTasks;
|
||||
|
||||
namespace OnecMonitor.Common.DTO.MaintenanceTasks;
|
||||
|
||||
[MessagePackObject]
|
||||
public class MaintenanceStepNodeDto
|
||||
{
|
||||
[Key(0)]
|
||||
public Guid Id { get; set; } = Guid.Empty;
|
||||
[Key(1)]
|
||||
public MaintenanceStepNodeKind Kind { get; set; }
|
||||
[Key(2)]
|
||||
public MaintenanceStepNodeDto? LeftNode { get; set; }
|
||||
[Key(3)]
|
||||
public MaintenanceStepNodeDto? RightNode { get; set; }
|
||||
[Key(4)]
|
||||
public MaintenanceStepDto Step { get; set; } = null!;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace OnecMonitor.Common.DTO.MaintenanceTasks;
|
||||
|
||||
public class MaintenanceStepNodeLogItemDto
|
||||
{
|
||||
[Key(0)]
|
||||
public Guid Id { get; set; }
|
||||
[Key(1)]
|
||||
public DateTime TimeStamp { get; set; }
|
||||
[Key(2)]
|
||||
public bool IsError { get; set; }
|
||||
[Key(3)]
|
||||
public bool IsFinish { get; set; }
|
||||
[Key(4)]
|
||||
public string Message { get; set; } = string.Empty;
|
||||
[Key(5)]
|
||||
public Guid InfoBaseId { get; set; }
|
||||
[Key(6)]
|
||||
public Guid StepNodeId { get; set; }
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace OnecMonitor.Common.DTO.MaintenanceTasks;
|
||||
|
||||
[MessagePackObject]
|
||||
public class MaintenanceTaskDto
|
||||
{
|
||||
[Key(0)]
|
||||
public Guid Id { get; set; } = Guid.Empty;
|
||||
[Key(1)]
|
||||
public MaintenanceStepNodeDto RootNode { get; set; } = null!;
|
||||
[Key(2)]
|
||||
public List<InfoBaseDto> InfoBases { get; set; } = [];
|
||||
}
|
@ -8,7 +8,6 @@
|
||||
|
||||
// Client messages
|
||||
AgentInfo,
|
||||
SubscribingForCommands,
|
||||
TechLogSeancesRequest,
|
||||
LastFilePositionRequest,
|
||||
TechLogEventContent,
|
||||
@ -21,6 +20,8 @@
|
||||
UpdateInfoBasesTaskRequest,
|
||||
SettingsRequest,
|
||||
|
||||
MaintenanceStepNodeLog,
|
||||
|
||||
// Server messages
|
||||
LastFilePosition,
|
||||
TechLogSeances,
|
||||
@ -33,6 +34,8 @@
|
||||
UpdateInfoBasesRequest,
|
||||
UpdateInfoBasesTask,
|
||||
UpdateSettingsRequest,
|
||||
Settings
|
||||
Settings,
|
||||
|
||||
MaintenanceTask,
|
||||
}
|
||||
}
|
||||
|
@ -10,13 +10,15 @@ public class V8FileDto
|
||||
[Key(1)]
|
||||
public string Name { get; set; }
|
||||
[Key(2)]
|
||||
public string FileExtension { get; set; }
|
||||
[Key(3)]
|
||||
public string Version { get; set; }
|
||||
[Key(3)]
|
||||
public bool IsUpdate { get; set; } = false;
|
||||
[Key(4)]
|
||||
public bool IsExtension { get; set; } = false;
|
||||
public bool IsUpdate { get; set; } = false;
|
||||
[Key(5)]
|
||||
public bool IsConfiguration { get; set; } = false;
|
||||
public bool IsExtension { get; set; } = false;
|
||||
[Key(6)]
|
||||
public bool IsConfiguration { get; set; } = false;
|
||||
[Key(7)]
|
||||
public byte[] Data { get; set; } = [];
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace OnecMonitor.Server.Extensions;
|
||||
namespace OnecMonitor.Common.Extensions;
|
||||
|
||||
public static class EnumExtension
|
||||
{
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.SymbolStore;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection.PortableExecutable;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Channels;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@ -16,11 +17,11 @@ public abstract class FastConnection(ILogger<FastConnection> logger) : IDisposab
|
||||
private CancellationToken _cancellationToken;
|
||||
|
||||
private readonly ConcurrentDictionary<Guid, TaskCompletionSource<Message>> _calls = new();
|
||||
private readonly Channel<Message> _inputChannel = Channel.CreateBounded<Message>(1000);
|
||||
private readonly Channel<Message> _outputChannel = Channel.CreateBounded<Message>(1000);
|
||||
private readonly Channel<Message> _messagesChannel = Channel.CreateUnbounded<Message>();
|
||||
private readonly SemaphoreSlim _disconnectingEventSemaphore = new(0);
|
||||
private bool _disposedValue;
|
||||
|
||||
|
||||
public event EventHandler<Message>? MessageReceived;
|
||||
protected internal event EventHandler? Disconnected;
|
||||
|
||||
private void RaiseDisconnected()
|
||||
@ -43,9 +44,6 @@ public abstract class FastConnection(ILogger<FastConnection> logger) : IDisposab
|
||||
_disconnectingEventSemaphore.Release();
|
||||
}
|
||||
|
||||
public async Task<Message> ReadMessage(CancellationToken cancellationToken)
|
||||
=> await _inputChannel.Reader.ReadAsync(cancellationToken);
|
||||
|
||||
public async Task SendOk(Message callMessage, CancellationToken cancellationToken)
|
||||
{
|
||||
var header = new MessageHeader(MessageType.Ok, 0, callMessage.Header.CallId);
|
||||
@ -115,16 +113,16 @@ public abstract class FastConnection(ILogger<FastConnection> logger) : IDisposab
|
||||
|
||||
private async Task<TResult> WriteMessageAndWaitResult<TResult>(Message message, MessageType responseMessageType, CancellationToken cancellationToken)
|
||||
{
|
||||
var cts = new TaskCompletionSource<Message>();
|
||||
var cts = new TaskCompletionSource<Message>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
_calls.TryAdd(message.Header.CallId, cts);
|
||||
|
||||
logger.LogTrace($"Постановка сообщения в очередь отправки. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}");
|
||||
await _outputChannel.Writer.WriteAsync(message, cancellationToken);
|
||||
await _messagesChannel.Writer.WriteAsync(message, cancellationToken);
|
||||
|
||||
logger.LogTrace($"Ожидание подтверждения на сообщение. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}");
|
||||
logger.LogTrace($"Ожидание подтверждения получения сообщения. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}");
|
||||
var result = await cts.Task.WaitAsync(cancellationToken);
|
||||
|
||||
logger.LogTrace($"Подтверждение сообщения получено. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}");
|
||||
logger.LogTrace($"Подтверждение получения сообщения получено. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}");
|
||||
if (result == null)
|
||||
throw new TimeoutException("Ошибка получения ответа на вызов");
|
||||
|
||||
@ -141,14 +139,14 @@ public abstract class FastConnection(ILogger<FastConnection> logger) : IDisposab
|
||||
private async Task WriteMessage(Message message, bool needWait, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!needWait)
|
||||
await _outputChannel.Writer.WriteAsync(message, cancellationToken);
|
||||
await _messagesChannel.Writer.WriteAsync(message, cancellationToken);
|
||||
else
|
||||
{
|
||||
var cts = new TaskCompletionSource<Message>();
|
||||
var cts = new TaskCompletionSource<Message>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
_calls.TryAdd(message.Header.CallId, cts);
|
||||
|
||||
logger.LogTrace($"Постановка сообщения в очередь отправки. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}");
|
||||
await _outputChannel.Writer.WriteAsync(message, cancellationToken);
|
||||
await _messagesChannel.Writer.WriteAsync(message, cancellationToken);
|
||||
|
||||
logger.LogTrace($"Ожидание подтверждения на сообщение. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}");
|
||||
var result = await cts.Task.WaitAsync(cancellationToken);
|
||||
@ -165,7 +163,29 @@ public abstract class FastConnection(ILogger<FastConnection> logger) : IDisposab
|
||||
throw new Exception($"Получено неожиданное сообщение. Ожидаемый тип: {MessageType.Ok}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartWritingToStream()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!_cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var message = await _messagesChannel.Reader.ReadAsync(_cancellationToken);
|
||||
|
||||
await Socket!.SendAsync(message.Header.AsMemory(), _cancellationToken);
|
||||
|
||||
if (message.Data.Length > 0)
|
||||
await Socket!.SendAsync(message.Data, _cancellationToken);
|
||||
|
||||
logger.LogTrace($"Отправлено сообщение. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
RaiseDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task WriteMessageToStream<T>(MessageType messageType, T item, CancellationToken cancellationToken)
|
||||
{
|
||||
var data = MessagePackSerializer.Serialize(item, cancellationToken: cancellationToken).AsMemory();
|
||||
@ -178,29 +198,7 @@ public abstract class FastConnection(ILogger<FastConnection> logger) : IDisposab
|
||||
if (header.Length > 0)
|
||||
await Socket!.SendAsync(data, cancellationToken);
|
||||
|
||||
logger.LogTrace($"Отправлено сообщение в поток. Тип: {header.Type}. Идентификатор: {header.CallId}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
RaiseDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartWritingToStream()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!_cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var message = await _outputChannel.Reader.ReadAsync(_cancellationToken);
|
||||
|
||||
await Socket!.SendAsync(message.Header.AsMemory(), _cancellationToken);
|
||||
|
||||
if (message.Data.Length > 0)
|
||||
await Socket!.SendAsync(message.Data, _cancellationToken);
|
||||
|
||||
logger.LogTrace($"Отправлено сообщение в поток. Тип: {message.Header.Type}. Идентификатор: {message.Header.CallId}");
|
||||
}
|
||||
logger.LogTrace($"Отправлено сообщение. Тип: {header.Type}. Идентификатор: {header.CallId}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -231,8 +229,10 @@ public abstract class FastConnection(ILogger<FastConnection> logger) : IDisposab
|
||||
|
||||
if (_calls.TryGetValue(message.Header.CallId, out var cts))
|
||||
cts.TrySetResult(message);
|
||||
else if (MessageReceived is not null)
|
||||
MessageReceived.Invoke(this, message);
|
||||
else
|
||||
await _inputChannel.Writer.WriteAsync(message, _cancellationToken);
|
||||
await SendError(message, "Не найдено подключенных обработчиков сообщений", _cancellationToken);
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait ContinueOnCapturedContext="false" />
|
||||
</Weavers>
|
@ -1,15 +1,15 @@
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace OnecMonitor.Server.Models.MaintenanceTasks;
|
||||
namespace OnecMonitor.Common.Models.MaintenanceTasks;
|
||||
|
||||
public enum MaintenanceStepKind
|
||||
{
|
||||
[Display(Name = "Блокировка сеансов")]
|
||||
[Display(Name = "Блокировка соединений")]
|
||||
LockConnections,
|
||||
[Display(Name = "Закрытие сеансов")]
|
||||
CloseConnections,
|
||||
[Display(Name = "Разблокировка сеансов")]
|
||||
[Display(Name = "Разблокировка соединений")]
|
||||
UnlockConnections,
|
||||
[Display(Name = "Загрузка расширения")]
|
||||
LoadExtension,
|
||||
@ -17,8 +17,6 @@ public enum MaintenanceStepKind
|
||||
UpdateConfiguration,
|
||||
[Display(Name = "Загрузка конфигурации")]
|
||||
LoadConfiguration,
|
||||
[Display(Name = "Обновление информационной базы")]
|
||||
UpdateDatabase,
|
||||
[Display(Name = "Запуск внешней обработки")]
|
||||
StartExternalDataProcessor
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace OnecMonitor.Server.Models.MaintenanceTasks;
|
||||
namespace OnecMonitor.Common.Models.MaintenanceTasks;
|
||||
|
||||
public enum MaintenanceStepNodeKind
|
||||
{
|
@ -1,34 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
@ -1,20 +1,17 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OnecMonitor.Common.DTO;
|
||||
|
||||
namespace OnecMonitor.Common;
|
||||
|
||||
public class ServerConnection(ILogger<ServerConnection> logger) : FastConnection(logger)
|
||||
{
|
||||
private ILogger<ServerConnection> _logger = null!;
|
||||
private string _host = null!;
|
||||
private int _port;
|
||||
|
||||
public event EventHandler? Connected;
|
||||
|
||||
protected void Start(string host, int port, ILogger<ServerConnection> logger, CancellationToken token)
|
||||
protected async Task Start(string host, int port, Func<Task> afterConnectCallback, CancellationToken token)
|
||||
{
|
||||
_logger = logger;
|
||||
_host = host;
|
||||
_port = port;
|
||||
|
||||
@ -23,22 +20,22 @@ public class ServerConnection(ILogger<ServerConnection> logger) : FastConnection
|
||||
logger.LogWarning("Отключен от сервера");
|
||||
|
||||
if (!token.IsCancellationRequested)
|
||||
_ = TryConnectInLoop(token);
|
||||
_ = TryConnectInLoop(afterConnectCallback, token);
|
||||
};
|
||||
|
||||
_ = TryConnectInLoop(token);
|
||||
await TryConnectInLoop(afterConnectCallback, token);
|
||||
}
|
||||
|
||||
private async Task TryConnectInLoop(CancellationToken cancellationToken)
|
||||
private async Task TryConnectInLoop(Func<Task> afterConnectCallback, CancellationToken cancellationToken)
|
||||
{
|
||||
await ConnectInLoop(cancellationToken);
|
||||
|
||||
await ConnectInLoop(afterConnectCallback, cancellationToken);
|
||||
|
||||
RunStreamLoops(cancellationToken);
|
||||
|
||||
_logger.LogTrace("Циклы потоков чтения/записи запущены");
|
||||
logger.LogTrace("Циклы потоков чтения/записи запущены");
|
||||
}
|
||||
|
||||
private async Task ConnectInLoop(CancellationToken cancellationToken)
|
||||
private async Task ConnectInLoop(Func<Task> afterConnectCallback, CancellationToken cancellationToken)
|
||||
{
|
||||
do
|
||||
{
|
||||
@ -46,11 +43,13 @@ public class ServerConnection(ILogger<ServerConnection> logger) : FastConnection
|
||||
{
|
||||
await Reconnect(cancellationToken);
|
||||
|
||||
_logger.LogTrace("Установлено соединение с сервером");
|
||||
logger.LogTrace("Установлено соединение с сервером");
|
||||
|
||||
await afterConnectCallback();
|
||||
}
|
||||
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.NotConnected)
|
||||
{
|
||||
_logger.LogTrace("Ошибка установки соединения с сервером");
|
||||
logger.LogTrace("Ошибка установки соединения с сервером");
|
||||
}
|
||||
|
||||
if (Socket?.Connected == true)
|
||||
@ -61,16 +60,17 @@ public class ServerConnection(ILogger<ServerConnection> logger) : FastConnection
|
||||
|
||||
private async Task Reconnect(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogTrace($"Попытка подключения к {_host}:{_port}");
|
||||
logger.LogTrace($"Попытка подключения к {_host}:{_port}");
|
||||
|
||||
Socket?.Dispose();
|
||||
Socket = null;
|
||||
|
||||
var addresses = await Dns.GetHostAddressesAsync(_host, AddressFamily.InterNetwork, cancellationToken);
|
||||
if (addresses.Length == 0)
|
||||
throw new Exception("Не удалось определить адрес сервера");
|
||||
var endPoint = new IPEndPoint(addresses[0], _port);
|
||||
|
||||
_logger.LogTrace($"Адрес сервера: {endPoint.Address}");
|
||||
logger.LogTrace($"Адрес сервера: {endPoint.Address}");
|
||||
|
||||
Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
|
||||
{
|
||||
@ -91,9 +91,5 @@ public class ServerConnection(ILogger<ServerConnection> logger) : FastConnection
|
||||
|
||||
if (!Socket.Connected)
|
||||
throw new SocketException((int)SocketError.NotConnected);
|
||||
|
||||
_logger.LogInformation("Подключен к серверу");
|
||||
|
||||
Connected?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
@ -5,13 +5,13 @@ namespace OnecMonitor.Common.TechLog
|
||||
{
|
||||
public static class TechLogParser
|
||||
{
|
||||
public static bool TryParse(AgentInstance agentInstance, TechLogEventContentDto item, out TjEvent tjEvent)
|
||||
public static bool TryParse(AgentInstanceDto agentInstanceDto, TechLogEventContentDto item, out TjEvent tjEvent)
|
||||
{
|
||||
var content = item.Content.AsSpan();
|
||||
|
||||
tjEvent = new TjEvent()
|
||||
{
|
||||
AgentId = agentInstance.Id,
|
||||
AgentId = agentInstanceDto.Id,
|
||||
SeanceId = item.SeanceId,
|
||||
Folder = item.Folder,
|
||||
File = item.File,
|
||||
@ -19,7 +19,7 @@ namespace OnecMonitor.Common.TechLog
|
||||
};
|
||||
|
||||
int offset;
|
||||
if (TryParseDateTime(content[..26], agentInstance, out var dateTime))
|
||||
if (TryParseDateTime(content[..26], agentInstanceDto, out var dateTime))
|
||||
{
|
||||
offset = 27;
|
||||
tjEvent.DateTime = dateTime;
|
||||
@ -57,11 +57,11 @@ namespace OnecMonitor.Common.TechLog
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryParseDateTime(ReadOnlySpan<char> content, AgentInstance agentInstance, out DateTime dateTime)
|
||||
private static bool TryParseDateTime(ReadOnlySpan<char> content, AgentInstanceDto agentInstanceDto, out DateTime dateTime)
|
||||
{
|
||||
if (DateTime.TryParse(content, out var eventDateTime))
|
||||
{
|
||||
dateTime = DateTime.SpecifyKind(eventDateTime.AddSeconds(-agentInstance.UtcOffset), DateTimeKind.Utc);
|
||||
dateTime = DateTime.SpecifyKind(eventDateTime.AddSeconds(-agentInstanceDto.UtcOffset), DateTimeKind.Utc);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
@ -14,7 +14,7 @@ namespace OnecMonitor.Common.TechLog
|
||||
{
|
||||
private readonly ILogger<TechLogProcessor> _logger;
|
||||
|
||||
private readonly ActionBlock<(AgentInstance, TechLogEventContentDto)> _parseblock;
|
||||
private readonly ActionBlock<(AgentInstanceDto, TechLogEventContentDto)> _parseblock;
|
||||
private readonly BatchBlock<TjEvent> _batchBlock;
|
||||
private readonly ActionBlock<TjEvent[]> _sendBlock;
|
||||
|
||||
@ -53,7 +53,7 @@ namespace OnecMonitor.Common.TechLog
|
||||
MaxDegreeOfParallelism = Environment.ProcessorCount,
|
||||
BoundedCapacity = 10000
|
||||
};
|
||||
_parseblock = new ActionBlock<(AgentInstance AgentInstance, TechLogEventContentDto Item)>(async i =>
|
||||
_parseblock = new ActionBlock<(AgentInstanceDto AgentInstance, TechLogEventContentDto Item)>(async i =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -83,9 +83,9 @@ namespace OnecMonitor.Common.TechLog
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessTjEventContent(AgentInstance agentInstance, TechLogEventContentDto tjEventContent, CancellationToken cancellationToken = default)
|
||||
public async Task ProcessTjEventContent(AgentInstanceDto agentInstanceDto, TechLogEventContentDto tjEventContent, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _parseblock.SendAsync((agentInstance, tjEventContent), cancellationToken);
|
||||
await _parseblock.SendAsync((agentInstance: agentInstanceDto, tjEventContent), cancellationToken);
|
||||
|
||||
_logger.LogTrace("Tj event content has been sent to the parsing block");
|
||||
}
|
||||
|
@ -10,12 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ClickHouse.Client" Version="7.10.0" />
|
||||
<PackageReference Include="ConfigureAwait.Fody" Version="3.3.2" PrivateAssets="All" />
|
||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||
<PackageReference Include="Fody" Version="6.9.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.67.0" />
|
||||
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.67.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.69.0">
|
||||
|
@ -25,6 +25,8 @@ namespace OnecMonitor.Server
|
||||
public DbSet<TechLogSettings> TechLogSettings { get; set; }
|
||||
public DbSet<MaintenanceTask> MaintenanceTasks { get; set; }
|
||||
public DbSet<MaintenanceStepNode> MaintenanceStepNodes { get; set; }
|
||||
public DbSet<MaintenanceStep> MaintenanceSteps { get; set; }
|
||||
public DbSet<MaintenanceStepNodeLogItem> MaintenanceStepNodeLogs { get; set; }
|
||||
|
||||
public AppDbContext(IHostEnvironment hostEnvironment)
|
||||
=> DbPath = Path.Join(hostEnvironment.ContentRootPath, "om-server.db");
|
||||
|
@ -1,9 +1,6 @@
|
||||
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.V8Files;
|
||||
using OnecMonitor.Server.ViewModels.Credentials;
|
||||
@ -96,7 +93,7 @@ public class CommonProfile : Profile
|
||||
.ForMember(c => c.Id, i => i.Ignore())
|
||||
.ForMember(c => c.InfoBases, i => i.Ignore());
|
||||
|
||||
CreateMap<MaintenanceTask, MaintenanceTaskListItemViewModel>().ReverseMap();
|
||||
CreateMap<MaintenanceTask, MaintenanceTaskListItemViewModel>();
|
||||
|
||||
CreateMap<MaintenanceStep, MaintenanceStepViewModel>()
|
||||
.ReverseMap()
|
||||
|
@ -16,6 +16,7 @@ public class DtoProfile : Profile
|
||||
|
||||
CreateMap<V8File, V8FileDto>()
|
||||
.ForMember(c => c.Data, opt => opt.MapFrom(src => File.ReadAllBytes(src.DataPath)))
|
||||
.ForMember(c => c.FileExtension, opt => opt.MapFrom(src => Path.GetExtension(src.DataPath)))
|
||||
.ReverseMap();
|
||||
|
||||
CreateMap<InfoBase, InfoBaseDto>().ReverseMap();
|
||||
|
@ -46,7 +46,7 @@ namespace OnecMonitor.Server.Controllers
|
||||
if (agent == null)
|
||||
return NotFound();
|
||||
|
||||
var agentConnection = connectionsManager.GetCommandsSubscriberConnection(agent.Id);
|
||||
var agentConnection = connectionsManager.GetAgentConnection(agent.Id);
|
||||
|
||||
var installedPlatforms = agentConnection == null ? [] : await agentConnection.GetInstalledPlatforms(cancellationToken);
|
||||
var ragents = agentConnection == null ? [] : await agentConnection.GetRagentServices(cancellationToken);
|
||||
|
@ -109,7 +109,7 @@ public class ClustersController(AppDbContext appDbContext, AgentsConnectionsMana
|
||||
var connectedAgents = connectionsManager.GetConnectedAgents(appDbContext.Agents.ToList());
|
||||
foreach (var agent in connectedAgents)
|
||||
{
|
||||
var commandsConnection = connectionsManager.GetCommandsSubscriberConnection(agent.Id);
|
||||
var commandsConnection = connectionsManager.GetAgentConnection(agent.Id);
|
||||
|
||||
if (commandsConnection == null)
|
||||
continue;
|
||||
|
@ -110,7 +110,7 @@ public class InfoBasesController(AppDbContext appDbContext, AgentsConnectionsMan
|
||||
var connectedAgents = connectionsManager.GetConnectedAgents(appDbContext.Agents.ToList());
|
||||
foreach (var agent in connectedAgents)
|
||||
{
|
||||
var commandsConnection = connectionsManager.GetCommandsSubscriberConnection(agent.Id);
|
||||
var commandsConnection = connectionsManager.GetAgentConnection(agent.Id);
|
||||
|
||||
if (commandsConnection == null)
|
||||
continue;
|
||||
|
@ -4,30 +4,39 @@ using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OnecMonitor.Common.Models.MaintenanceTasks;
|
||||
using OnecMonitor.Server.Helpers;
|
||||
using OnecMonitor.Server.Models;
|
||||
using OnecMonitor.Server.Models.MaintenanceTasks;
|
||||
using OnecMonitor.Server.Services;
|
||||
using OnecMonitor.Server.ViewModels;
|
||||
using OnecMonitor.Server.ViewModels.MaintenanceTasks;
|
||||
|
||||
namespace OnecMonitor.Server.Controllers;
|
||||
|
||||
public class MaintenanceTasksController(AppDbContext appDbContext, IMapper mapper) : Controller
|
||||
public class MaintenanceTasksController(AppDbContext appDbContext, AgentsConnectionsManager connectionsManager, IMapper mapper) : Controller
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
|
||||
public async Task<IActionResult> Index(CancellationToken cancellationToken)
|
||||
=> View(new MaintenanceTasksIndexViewModel
|
||||
{
|
||||
var items = await appDbContext.MaintenanceTasks
|
||||
.AsNoTracking()
|
||||
.Include(c => c.RootNode)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
foreach (var item in items)
|
||||
await LoadNodesRecursively(item.RootNode, false, cancellationToken);
|
||||
|
||||
return View(new MaintenanceTasksIndexViewModel
|
||||
{
|
||||
Items = await appDbContext.MaintenanceTasks
|
||||
.AsNoTracking()
|
||||
.ProjectTo<MaintenanceTaskListItemViewModel>(mapper.ConfigurationProvider)
|
||||
.ToListAsync(cancellationToken)
|
||||
Items = mapper.Map<List<MaintenanceTaskListItemViewModel>>(items)
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Edit(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
@ -45,7 +54,7 @@ public class MaintenanceTasksController(AppDbContext appDbContext, IMapper mappe
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
|
||||
await LoadNodesRecursively(model.RootNode, cancellationToken);
|
||||
await LoadNodesRecursively(model.RootNode, false, cancellationToken);
|
||||
|
||||
var vm = mapper.Map<MaintenanceTaskEditViewModel>(model);
|
||||
var rootNodeVm = mapper.Map<MaintenanceStepNodeViewModel>(model.RootNode);
|
||||
@ -67,21 +76,30 @@ public class MaintenanceTasksController(AppDbContext appDbContext, IMapper mappe
|
||||
Id = Guid.NewGuid()
|
||||
} : await appDbContext.MaintenanceTasks
|
||||
.Include(c => c.RootNode)
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(i => i.Id == vm.Id, cancellationToken);
|
||||
|
||||
if (model == null)
|
||||
return NotFound();
|
||||
|
||||
mapper.Map(vm, model);
|
||||
model.RootNode = JsonSerializer.Deserialize<MaintenanceStepNode>(vm.SerializedStepNode, _jsonOptions)!;
|
||||
model.RootNodeId = model.RootNode.Id;
|
||||
if (!isNew)
|
||||
await LoadNodesRecursively(model.RootNode, false, cancellationToken);
|
||||
|
||||
var rootNode = JsonSerializer.Deserialize<MaintenanceStepNode>(vm.SerializedStepNode, _jsonOptions)!;
|
||||
|
||||
mapper.Map(vm, model);
|
||||
model.RootNodeId = rootNode.Id;
|
||||
|
||||
var newNodes = GetNodesList(rootNode);
|
||||
var oldNodes = GetNodesList(model.RootNode);
|
||||
|
||||
await UiHelper.UpdateModelItems(appDbContext.MaintenanceStepNodes, newNodes, oldNodes, cancellationToken);
|
||||
await UiHelper.UpdateModelItems(appDbContext.InfoBases, vm.InfoBases, model.InfoBases, cancellationToken);
|
||||
|
||||
model.RootNode = null!;
|
||||
|
||||
if (isNew)
|
||||
appDbContext.MaintenanceTasks.Add(model);
|
||||
else
|
||||
appDbContext.MaintenanceTasks.Update(model);
|
||||
|
||||
await appDbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
@ -128,6 +146,60 @@ public class MaintenanceTasksController(AppDbContext appDbContext, IMapper mappe
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Start(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var task = await appDbContext.MaintenanceTasks
|
||||
.AsNoTracking()
|
||||
.Include(c => c.RootNode)
|
||||
.ThenInclude(c => c.Step)
|
||||
.AsNoTracking()
|
||||
.Include(c => c.InfoBases)
|
||||
.ThenInclude(infoBase => infoBase.Cluster)
|
||||
.ThenInclude(cluster => cluster.Agent)
|
||||
.FirstOrDefaultAsync(c => c.Id == id, cancellationToken);
|
||||
|
||||
if (task == null)
|
||||
return NotFound();
|
||||
|
||||
await LoadNodesRecursively(task.RootNode, true, cancellationToken);
|
||||
|
||||
var affectedAgents = task.InfoBases.Select(c => c.Cluster.Agent).Distinct().ToList();
|
||||
var connectedAgents = connectionsManager.GetAgentsConnections(affectedAgents);
|
||||
|
||||
foreach (var connection in connectedAgents)
|
||||
await connection.StartMaintenanceTask(task, cancellationToken);
|
||||
|
||||
var taskToUpdate = await appDbContext.MaintenanceTasks.FindAsync([id], cancellationToken);
|
||||
taskToUpdate!.StartDateTime = DateTime.Now;
|
||||
await appDbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return View("Error", new ErrorViewModel(e.Message));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = await appDbContext.MaintenanceTasks.FirstOrDefaultAsync(c => c.Id == id, cancellationToken);
|
||||
appDbContext.MaintenanceTasks.Remove(item!);
|
||||
|
||||
await appDbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return View("Error", new ErrorViewModel(ex.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<MaintenanceTaskEditViewModel> PrepareViewModel(MaintenanceTaskEditViewModel vm, CancellationToken cancellationToken)
|
||||
{
|
||||
@ -159,24 +231,56 @@ public class MaintenanceTasksController(AppDbContext appDbContext, IMapper mappe
|
||||
return vm;
|
||||
}
|
||||
|
||||
private async Task LoadNodesRecursively(MaintenanceStepNode node, CancellationToken cancellationToken)
|
||||
private async Task LoadNodesRecursively(MaintenanceStepNode node, bool includeFully, CancellationToken cancellationToken)
|
||||
{
|
||||
if (node.LeftNodeId != null && node.LeftNodeId != Guid.Empty)
|
||||
node.LeftNode = await LoadNodeRecursively(node.LeftNodeId, cancellationToken);
|
||||
node.LeftNode = await LoadNodeRecursively(node.LeftNodeId, includeFully, cancellationToken);
|
||||
|
||||
if (node.RightNodeId != null && node.RightNodeId != Guid.Empty)
|
||||
node.RightNode = await LoadNodeRecursively(node.RightNodeId, cancellationToken);
|
||||
node.RightNode = await LoadNodeRecursively(node.RightNodeId, includeFully, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<MaintenanceStepNode> LoadNodeRecursively(Guid? id, CancellationToken cancellationToken)
|
||||
private async Task<MaintenanceStepNode> LoadNodeRecursively(Guid? id, bool includeFully, CancellationToken cancellationToken)
|
||||
{
|
||||
var node = (await appDbContext.MaintenanceStepNodes
|
||||
var query = appDbContext.MaintenanceStepNodes
|
||||
.AsNoTracking()
|
||||
.Include(c => c.Step)
|
||||
.FirstOrDefaultAsync(c => c.Id == id, cancellationToken))!;
|
||||
|
||||
await LoadNodesRecursively(node, cancellationToken);
|
||||
.Include(c => c.Step);
|
||||
|
||||
return node;
|
||||
var fullQuery = includeFully switch
|
||||
{
|
||||
true => query
|
||||
.ThenInclude(c => c.File)
|
||||
.FirstOrDefaultAsync(c => c.Id == id, cancellationToken),
|
||||
_ => query.FirstOrDefaultAsync(c => c.Id == id, cancellationToken),
|
||||
};
|
||||
|
||||
var node = await fullQuery;
|
||||
|
||||
await LoadNodesRecursively(node!, includeFully, cancellationToken);
|
||||
|
||||
return node!;
|
||||
}
|
||||
|
||||
private static List<MaintenanceStepNode> GetNodesList(MaintenanceStepNode node)
|
||||
{
|
||||
var list = new List<MaintenanceStepNode>();
|
||||
FillNodesList(node, list);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static void FillNodesList(MaintenanceStepNode node, List<MaintenanceStepNode> list)
|
||||
{
|
||||
list.Add(node);
|
||||
|
||||
if (node.LeftNodeId != null && node.LeftNodeId != Guid.Empty)
|
||||
{
|
||||
FillNodesList(node.LeftNode, list);
|
||||
}
|
||||
|
||||
if (node.RightNodeId != null && node.RightNodeId != Guid.Empty)
|
||||
{
|
||||
FillNodesList(node.RightNode, list);
|
||||
}
|
||||
}
|
||||
}
|
@ -140,7 +140,8 @@ namespace OnecMonitor.Server.Controllers
|
||||
model.Agents.Where(c => !newConnectedAgents.Contains(c)).ToList().ForEach(c => model.Agents.Remove(c));
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
await connectionsManager.UpdateTechLogSeances(affectedAgents, cancellationToken);
|
||||
|
||||
await RequestTechLogSeancesUpdating(affectedAgents, cancellationToken);
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
@ -163,7 +164,7 @@ namespace OnecMonitor.Server.Controllers
|
||||
await dbContext.Database.CommitTransactionAsync(cancellationToken);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
await connectionsManager.UpdateTechLogSeances(item.Agents, cancellationToken);
|
||||
await RequestTechLogSeancesUpdating(item.Agents, cancellationToken);
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
@ -173,6 +174,21 @@ namespace OnecMonitor.Server.Controllers
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RequestTechLogSeancesUpdating(List<Agent> agents, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var connection in connectionsManager.GetAgentsConnections(agents))
|
||||
{
|
||||
try
|
||||
{
|
||||
await connection.RequestTechLogSeancesUpdating(cancellationToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IActionResult> TechLog(Guid id, int pageNumber = 1, [FromQuery] string filter = "", CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
@ -66,7 +66,7 @@ public class TechLogSettingsController(AppDbContext appDbContext, ITechLogStorag
|
||||
await appDbContext.Database.CommitTransactionAsync(cancellationToken);
|
||||
|
||||
var agents = connectionsManager.GetConnectedAgents(await appDbContext.Agents.ToListAsync(cancellationToken));
|
||||
var subscribers = connectionsManager.GetCommandSubscribers(agents);
|
||||
var subscribers = connectionsManager.GetAgentsConnections(agents);
|
||||
foreach (var subscriber in subscribers)
|
||||
await subscriber.SendUpdateSettingsRequest(cancellationToken);
|
||||
|
||||
|
@ -102,7 +102,7 @@ public class UpdateInfoBaseTasksController(AppDbContext appDbContext, AgentsConn
|
||||
|
||||
foreach (var agent in connectedAgents)
|
||||
{
|
||||
var commandsConnection = connectionsManager.GetCommandsSubscriberConnection(agent.Id)!;
|
||||
var commandsConnection = connectionsManager.GetAgentConnection(agent.Id)!;
|
||||
await commandsConnection.RequestInfoBasesUpdating(cancellationToken);
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,10 @@
|
||||
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.V8Files;
|
||||
using OneSTools.Common.Platform.Unpack;
|
||||
|
||||
namespace OnecMonitor.Server.Controllers;
|
||||
|
||||
|
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait ContinueOnCapturedContext="false" />
|
||||
</Weavers>
|
@ -76,6 +76,31 @@ public static class UiHelper
|
||||
.ToList()
|
||||
.ForEach(c => modelItems.Remove(c));
|
||||
}
|
||||
|
||||
public static async Task UpdateModelItems<T>(
|
||||
IQueryable<T> queryable,
|
||||
List<T> newItems,
|
||||
List<T> oldItems,
|
||||
CancellationToken cancellationToken) where T : DatabaseObject
|
||||
{
|
||||
var ids = newItems.Select(c => c.Id);
|
||||
|
||||
var addedItems = await queryable
|
||||
.Where(c => ids.Contains(c.Id))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// add new
|
||||
addedItems
|
||||
.Where(c => !oldItems.Contains(c))
|
||||
.ToList()
|
||||
.ForEach(oldItems.Add);
|
||||
|
||||
// remove deleted
|
||||
oldItems
|
||||
.Where(c => !addedItems.Contains(c))
|
||||
.ToList()
|
||||
.ForEach(c => oldItems.Remove(c));
|
||||
}
|
||||
|
||||
private static T? GetAttributeOfType<T>(this Enum enumVal) where T : Attribute
|
||||
{
|
||||
|
@ -10,7 +10,7 @@ using OnecMonitor.Server;
|
||||
namespace OnecMonitor.Server.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20250225183414_Initial")]
|
||||
[Migration("20250227193339_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@ -237,7 +237,7 @@ namespace OnecMonitor.Server.Migrations
|
||||
|
||||
b.HasIndex("FileId");
|
||||
|
||||
b.ToTable("MaintenanceStep");
|
||||
b.ToTable("MaintenanceSteps");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", b =>
|
||||
@ -270,6 +270,42 @@ namespace OnecMonitor.Server.Migrations
|
||||
b.ToTable("MaintenanceStepNodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNodeLogItem", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("InfoBaseId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsError")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsFinish")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StepNodeId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("TimeStamp")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InfoBaseId");
|
||||
|
||||
b.HasIndex("StepNodeId");
|
||||
|
||||
b.ToTable("MaintenanceStepNodeLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceTask", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -281,10 +317,19 @@ namespace OnecMonitor.Server.Migrations
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("FinishDateTime")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsFaulted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("RootNodeId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("StartDateTime")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RootNodeId");
|
||||
@ -597,6 +642,25 @@ namespace OnecMonitor.Server.Migrations
|
||||
b.Navigation("Step");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNodeLogItem", b =>
|
||||
{
|
||||
b.HasOne("OnecMonitor.Server.Models.InfoBase", "InfoBase")
|
||||
.WithMany()
|
||||
.HasForeignKey("InfoBaseId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", "StepNode")
|
||||
.WithMany("Logs")
|
||||
.HasForeignKey("StepNodeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("InfoBase");
|
||||
|
||||
b.Navigation("StepNode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceTask", b =>
|
||||
{
|
||||
b.HasOne("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", "RootNode")
|
||||
@ -659,6 +723,11 @@ namespace OnecMonitor.Server.Migrations
|
||||
b.Navigation("InfoBases");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", b =>
|
||||
{
|
||||
b.Navigation("Logs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceTask", b =>
|
||||
{
|
||||
b.Navigation("InfoBases");
|
@ -205,7 +205,7 @@ namespace OnecMonitor.Server.Migrations
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MaintenanceStep",
|
||||
name: "MaintenanceSteps",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
@ -216,9 +216,9 @@ namespace OnecMonitor.Server.Migrations
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MaintenanceStep", x => x.Id);
|
||||
table.PrimaryKey("PK_MaintenanceSteps", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_MaintenanceStep_V8Files_FileId",
|
||||
name: "FK_MaintenanceSteps_V8Files_FileId",
|
||||
column: x => x.FileId,
|
||||
principalTable: "V8Files",
|
||||
principalColumn: "Id");
|
||||
@ -272,9 +272,9 @@ namespace OnecMonitor.Server.Migrations
|
||||
principalTable: "MaintenanceStepNodes",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_MaintenanceStepNodes_MaintenanceStep_StepId",
|
||||
name: "FK_MaintenanceStepNodes_MaintenanceSteps_StepId",
|
||||
column: x => x.StepId,
|
||||
principalTable: "MaintenanceStep",
|
||||
principalTable: "MaintenanceSteps",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
@ -284,6 +284,9 @@ namespace OnecMonitor.Server.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
StartDateTime = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
IsFaulted = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
FinishDateTime = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
Description = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
|
||||
RootNodeId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
@ -357,6 +360,35 @@ namespace OnecMonitor.Server.Migrations
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MaintenanceStepNodeLogs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
TimeStamp = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
IsError = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
IsFinish = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
Message = table.Column<string>(type: "TEXT", nullable: false),
|
||||
InfoBaseId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
StepNodeId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MaintenanceStepNodeLogs", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_MaintenanceStepNodeLogs_InfoBases_InfoBaseId",
|
||||
column: x => x.InfoBaseId,
|
||||
principalTable: "InfoBases",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_MaintenanceStepNodeLogs_MaintenanceStepNodes_StepNodeId",
|
||||
column: x => x.StepNodeId,
|
||||
principalTable: "MaintenanceStepNodes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UpdateInfoBaseTaskLogItems",
|
||||
columns: table => new
|
||||
@ -427,9 +459,14 @@ namespace OnecMonitor.Server.Migrations
|
||||
column: "TemplatesId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MaintenanceStep_FileId",
|
||||
table: "MaintenanceStep",
|
||||
column: "FileId");
|
||||
name: "IX_MaintenanceStepNodeLogs_InfoBaseId",
|
||||
table: "MaintenanceStepNodeLogs",
|
||||
column: "InfoBaseId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MaintenanceStepNodeLogs_StepNodeId",
|
||||
table: "MaintenanceStepNodeLogs",
|
||||
column: "StepNodeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MaintenanceStepNodes_LeftNodeId",
|
||||
@ -446,6 +483,11 @@ namespace OnecMonitor.Server.Migrations
|
||||
table: "MaintenanceStepNodes",
|
||||
column: "StepId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MaintenanceSteps_FileId",
|
||||
table: "MaintenanceSteps",
|
||||
column: "FileId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MaintenanceTasks_RootNodeId",
|
||||
table: "MaintenanceTasks",
|
||||
@ -479,6 +521,9 @@ namespace OnecMonitor.Server.Migrations
|
||||
migrationBuilder.DropTable(
|
||||
name: "LogTemplateTechLogSeance");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "MaintenanceStepNodeLogs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TechLogFilters");
|
||||
|
||||
@ -519,7 +564,7 @@ namespace OnecMonitor.Server.Migrations
|
||||
name: "MaintenanceStepNodes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "MaintenanceStep");
|
||||
name: "MaintenanceSteps");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "V8Files");
|
@ -234,7 +234,7 @@ namespace OnecMonitor.Server.Migrations
|
||||
|
||||
b.HasIndex("FileId");
|
||||
|
||||
b.ToTable("MaintenanceStep");
|
||||
b.ToTable("MaintenanceSteps");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", b =>
|
||||
@ -267,6 +267,42 @@ namespace OnecMonitor.Server.Migrations
|
||||
b.ToTable("MaintenanceStepNodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNodeLogItem", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("InfoBaseId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsError")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsFinish")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StepNodeId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("TimeStamp")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InfoBaseId");
|
||||
|
||||
b.HasIndex("StepNodeId");
|
||||
|
||||
b.ToTable("MaintenanceStepNodeLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceTask", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@ -278,10 +314,19 @@ namespace OnecMonitor.Server.Migrations
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("FinishDateTime")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsFaulted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("RootNodeId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("StartDateTime")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RootNodeId");
|
||||
@ -594,6 +639,25 @@ namespace OnecMonitor.Server.Migrations
|
||||
b.Navigation("Step");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNodeLogItem", b =>
|
||||
{
|
||||
b.HasOne("OnecMonitor.Server.Models.InfoBase", "InfoBase")
|
||||
.WithMany()
|
||||
.HasForeignKey("InfoBaseId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", "StepNode")
|
||||
.WithMany("Logs")
|
||||
.HasForeignKey("StepNodeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("InfoBase");
|
||||
|
||||
b.Navigation("StepNode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceTask", b =>
|
||||
{
|
||||
b.HasOne("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", "RootNode")
|
||||
@ -656,6 +720,11 @@ namespace OnecMonitor.Server.Migrations
|
||||
b.Navigation("InfoBases");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceStepNode", b =>
|
||||
{
|
||||
b.Navigation("Logs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("OnecMonitor.Server.Models.MaintenanceTasks.MaintenanceTask", b =>
|
||||
{
|
||||
b.Navigation("InfoBases");
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using OnecMonitor.Common.Models.MaintenanceTasks;
|
||||
|
||||
namespace OnecMonitor.Server.Models.MaintenanceTasks;
|
||||
|
||||
@ -12,5 +13,5 @@ public class MaintenanceStep : DatabaseObject
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public Guid? FileId { get; set; }
|
||||
|
||||
public V8File? File { get; set; } = null!;
|
||||
public V8File? File { get; set; }
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using OnecMonitor.Common.Models.MaintenanceTasks;
|
||||
|
||||
namespace OnecMonitor.Server.Models.MaintenanceTasks;
|
||||
|
||||
@ -16,4 +17,6 @@ public class MaintenanceStepNode : DatabaseObject
|
||||
public MaintenanceStepNode RightNode { get; set; } = null!;
|
||||
[ForeignKey(nameof(StepId))]
|
||||
public MaintenanceStep Step { get; set; } = null!;
|
||||
|
||||
public virtual List<MaintenanceStepNodeLogItem> Logs { get; set; } = [];
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using OnecMonitor.Server.Models.MaintenanceTasks;
|
||||
|
||||
namespace OnecMonitor.Server.Models.MaintenanceTasks;
|
||||
|
||||
public class MaintenanceStepNodeLogItem : DatabaseObject
|
||||
{
|
||||
[DataType(DataType.Date)]
|
||||
public DateTime TimeStamp { get; set; }
|
||||
public bool IsError { get; set; }
|
||||
public bool IsFinish { get; set; }
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public Guid InfoBaseId { get; set; }
|
||||
public Guid StepNodeId { get; set; }
|
||||
|
||||
[ForeignKey(nameof(InfoBaseId))]
|
||||
public virtual InfoBase InfoBase { get; set; } = null!;
|
||||
[ForeignKey(nameof(StepNodeId))]
|
||||
public virtual MaintenanceStepNode StepNode { get; set; } = null!;
|
||||
}
|
@ -5,6 +5,9 @@ namespace OnecMonitor.Server.Models.MaintenanceTasks;
|
||||
|
||||
public class MaintenanceTask : DatabaseObject
|
||||
{
|
||||
public DateTime StartDateTime { get; set; } = DateTime.MinValue;
|
||||
public bool IsFaulted { get; set; } = false;
|
||||
public DateTime FinishDateTime { get; set; } = DateTime.MinValue;
|
||||
[MaxLength(200)]
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public Guid RootNodeId { get; set; }
|
||||
|
@ -29,9 +29,9 @@ builder.WebHost.ConfigureKestrel((context, options) =>
|
||||
var host = context.Configuration.GetValue("OnecMonitor:Http:Host", "0.0.0.0")!;
|
||||
var port = context.Configuration.GetValue("OnecMonitor:Http:Port", 7002);
|
||||
|
||||
options.Listen(IPAddress.Parse(host), port, options =>
|
||||
options.Listen(IPAddress.Parse(host), port, configure =>
|
||||
{
|
||||
options.Protocols = HttpProtocols.Http1;
|
||||
configure.Protocols = HttpProtocols.Http1;
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -13,7 +13,9 @@ using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using OnecMonitor.Common.DTO.MaintenanceTasks;
|
||||
using OnecMonitor.Server.Helpers;
|
||||
using OnecMonitor.Server.Models.MaintenanceTasks;
|
||||
using OneSTools.Common.Platform;
|
||||
using OneSTools.Common.Platform.RemoteAdministration;
|
||||
using OneSTools.Common.Platform.Services;
|
||||
@ -30,14 +32,11 @@ namespace OnecMonitor.Server.Services
|
||||
private readonly ILogger<AgentConnection> _logger;
|
||||
|
||||
public Guid ConnectionId { get; }
|
||||
public AgentInstance? AgentInstance { get; private set; }
|
||||
public AgentInstanceDto? AgentInstance { get; private set; }
|
||||
|
||||
public delegate void AgentConnectedHandler(AgentConnection agentConnection);
|
||||
public event AgentConnectedHandler? AgentConnected;
|
||||
|
||||
public delegate void AgentSubscribedForCommandsHandler(AgentConnection agentConnection);
|
||||
public event AgentSubscribedForCommandsHandler? SubscribedForCommands;
|
||||
|
||||
public delegate void AgentDisconnectedHandler(AgentConnection agentConnection);
|
||||
public event AgentDisconnectedHandler? AgentDisconnected;
|
||||
|
||||
@ -60,69 +59,52 @@ namespace OnecMonitor.Server.Services
|
||||
_logger = _agentScope.ServiceProvider.GetRequiredService<ILogger<AgentConnection>>();
|
||||
}
|
||||
|
||||
public async Task Listen(CancellationToken cancellationToken)
|
||||
public void Listen(CancellationToken cancellationToken)
|
||||
{
|
||||
RunStreamLoops(cancellationToken);
|
||||
|
||||
// first message must be an init message
|
||||
var firstMessage = await ReadMessage(cancellationToken);
|
||||
|
||||
if (firstMessage.Header.Type != MessageType.AgentInfo)
|
||||
{
|
||||
Socket?.Close();
|
||||
throw new Exception("First message must be \"Agent info\", connection closed");
|
||||
}
|
||||
|
||||
await HandleInitMessage(firstMessage.Data, cancellationToken);
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
MessageReceived += async (_, message) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var message = await ReadMessage(cancellationToken);
|
||||
|
||||
try
|
||||
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
|
||||
switch (message.Header.Type)
|
||||
{
|
||||
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
|
||||
switch (message.Header.Type)
|
||||
{
|
||||
case MessageType.TechLogEventContent:
|
||||
await HandleTechLogEventContent(message.Data, cancellationToken);
|
||||
break;
|
||||
case MessageType.LastFilePositionRequest:
|
||||
await HandleLastFilePositionRequest(message, cancellationToken);
|
||||
break;
|
||||
case MessageType.TechLogSeancesRequest:
|
||||
await UpdateTechLogSeances(message, cancellationToken);
|
||||
break;
|
||||
case MessageType.SubscribingForCommands:
|
||||
await HandleSubscribingForCommands(message, cancellationToken);
|
||||
break;
|
||||
case MessageType.UpdateInfoBasesTaskRequest:
|
||||
await HandleUpdateInfoBasesTaskRequest(message, cancellationToken);
|
||||
break;
|
||||
case MessageType.UpdateInfoBaseTaskLog:
|
||||
await HandleUpdateInfoBasesTaskLog(message, cancellationToken);
|
||||
break;
|
||||
case MessageType.SettingsRequest:
|
||||
await HandleSettingsRequest(message, cancellationToken);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Получено неожиданное сообщение: {message.Header.Type}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) {}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogTrace(ex, "Ошибка обработки сообщения");
|
||||
await SendError(message, ex.ToString(), cancellationToken);
|
||||
case MessageType.AgentInfo:
|
||||
await HandleInitMessage(message, cancellationToken);
|
||||
break;
|
||||
case MessageType.TechLogEventContent:
|
||||
await HandleTechLogEventContent(message.Data, cancellationToken);
|
||||
break;
|
||||
case MessageType.LastFilePositionRequest:
|
||||
await HandleLastFilePositionRequest(message, cancellationToken);
|
||||
break;
|
||||
case MessageType.TechLogSeancesRequest:
|
||||
await UpdateTechLogSeances(message, cancellationToken);
|
||||
break;
|
||||
case MessageType.UpdateInfoBasesTaskRequest:
|
||||
await HandleUpdateInfoBasesTaskRequest(message, cancellationToken);
|
||||
break;
|
||||
case MessageType.UpdateInfoBaseTaskLog:
|
||||
await HandleUpdateInfoBasesTaskLog(message, cancellationToken);
|
||||
break;
|
||||
case MessageType.SettingsRequest:
|
||||
await HandleSettingsRequest(message, cancellationToken);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Получено неожиданное сообщение: {message.Header.Type}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) {}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogTrace(ex, "Ошибка обработки сообщения");
|
||||
await SendError(message, ex.ToString(), cancellationToken);
|
||||
}
|
||||
};
|
||||
|
||||
RunStreamLoops(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task HandleSettingsRequest(Message message, CancellationToken cancellationToken)
|
||||
|
||||
private async Task HandleSettingsRequest(Message message, CancellationToken cancellationToken)
|
||||
{
|
||||
var tjSettings = await _appDbContext.TechLogSettings.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
@ -190,6 +172,9 @@ namespace OnecMonitor.Server.Services
|
||||
|
||||
public async Task RequestInfoBasesUpdating(CancellationToken cancellationToken)
|
||||
=> await Send(MessageType.UpdateInfoBasesRequest, cancellationToken);
|
||||
|
||||
public async Task StartMaintenanceTask(MaintenanceTask task, CancellationToken cancellationToken)
|
||||
=> await Send(MessageType.MaintenanceTask, _mapper.Map<MaintenanceTaskDto>(task), cancellationToken);
|
||||
|
||||
private async Task UpdateTechLogSeances(Message callMessage, CancellationToken cancellationToken)
|
||||
{
|
||||
@ -204,21 +189,21 @@ namespace OnecMonitor.Server.Services
|
||||
|
||||
var seances = new List<TechLogSeanceDto>();
|
||||
|
||||
agentSeances.ForEach(c =>
|
||||
agentSeances.ForEach(seance =>
|
||||
{
|
||||
StringBuilder templateBuilder = new();
|
||||
|
||||
c.Templates.ForEach(c =>
|
||||
seance.Templates.ForEach(template =>
|
||||
{
|
||||
// add template id and combine templates
|
||||
templateBuilder.AppendLine(c.Content.Replace("{LOG_PATH}", $"{{LOG_PATH}}{c.Id}"));
|
||||
templateBuilder.AppendLine(template.Content.Replace("{LOG_PATH}", $"{{LOG_PATH}}{template.Id}"));
|
||||
});
|
||||
|
||||
seances.Add(new TechLogSeanceDto()
|
||||
{
|
||||
Id = c.Id,
|
||||
StartDateTime = c.StartDateTime,
|
||||
FinishDateTime = c.FinishDateTime,
|
||||
Id = seance.Id,
|
||||
StartDateTime = seance.StartDateTime,
|
||||
FinishDateTime = seance.FinishDateTime,
|
||||
Template = templateBuilder.ToString()
|
||||
});
|
||||
});
|
||||
@ -226,9 +211,9 @@ namespace OnecMonitor.Server.Services
|
||||
await Send(MessageType.TechLogSeances, seances, callMessage, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task HandleInitMessage(ReadOnlyMemory<byte> messageData, CancellationToken cancellationToken)
|
||||
private async Task HandleInitMessage(Message message, CancellationToken cancellationToken)
|
||||
{
|
||||
AgentInstance = ParseMessageData<AgentInstance>(messageData, cancellationToken);
|
||||
AgentInstance = ParseMessageData<AgentInstanceDto>(message.Data, cancellationToken);
|
||||
|
||||
await _appDbContext.Database.BeginTransactionAsync(cancellationToken);
|
||||
|
||||
@ -260,20 +245,12 @@ namespace OnecMonitor.Server.Services
|
||||
|
||||
AgentConnected?.Invoke(this);
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch
|
||||
{
|
||||
await _appDbContext.Database.RollbackTransactionAsync(cancellationToken);
|
||||
|
||||
throw new RpcException(Status.DefaultCancelled, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleSubscribingForCommands(Message requestMessage, CancellationToken cancellationToken)
|
||||
{
|
||||
SubscribedForCommands?.Invoke(this);
|
||||
await SendOk(requestMessage, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task HandleLastFilePositionRequest(Message requestMessage, CancellationToken cancellationToken)
|
||||
{
|
||||
var request = ParseMessageData<LastFilePositionRequestDto>(requestMessage.Data, cancellationToken);
|
||||
|
@ -4,6 +4,7 @@ using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OnecMonitor.Common.DTO;
|
||||
|
||||
namespace OnecMonitor.Server.Services
|
||||
{
|
||||
@ -11,17 +12,13 @@ namespace OnecMonitor.Server.Services
|
||||
IConfiguration configuration,
|
||||
IServiceProvider serviceProvider,
|
||||
TechLogProcessor techLogProcessor,
|
||||
ILogger<AgentsConnectionsManager> logger)
|
||||
: BackgroundService
|
||||
ILogger<AgentsConnectionsManager> logger) : BackgroundService
|
||||
{
|
||||
private readonly string _host = configuration.GetValue("OnecMonitor:Tcp:Host", "0.0.0.0");
|
||||
private readonly int _port = configuration.GetValue("OnecMonitor:Tcp:Port", 7001);
|
||||
private readonly Socket _socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
|
||||
// k - agent id, v - connection id
|
||||
private readonly ConcurrentDictionary<Guid, Guid> _commandsSubscribers = new();
|
||||
|
||||
private ConcurrentDictionary<Guid, AgentConnection> Connections { get; } = new();
|
||||
|
||||
private readonly HashSet<AgentConnection> _connections = [];
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
@ -38,9 +35,8 @@ namespace OnecMonitor.Server.Services
|
||||
var agentConnection = new AgentConnection(client, techLogProcessor, serviceProvider);
|
||||
agentConnection.AgentConnected += AgentConnection_Connected;
|
||||
agentConnection.AgentDisconnected += AgentConnection_Disconnected;
|
||||
agentConnection.SubscribedForCommands += AgentConnection_SubscribedForCommands;
|
||||
|
||||
_ = agentConnection.Listen(stoppingToken);
|
||||
agentConnection.Listen(stoppingToken);
|
||||
}
|
||||
|
||||
_socket.Close();
|
||||
@ -48,62 +44,40 @@ namespace OnecMonitor.Server.Services
|
||||
|
||||
private void AgentConnection_Connected(AgentConnection agentConnection)
|
||||
{
|
||||
Connections.TryAdd(agentConnection.ConnectionId, agentConnection);
|
||||
|
||||
logger.LogInformation($"Агент подключился: {agentConnection.AgentInstance!.InstanceName}");
|
||||
lock (_connections)
|
||||
_connections.Add(agentConnection);
|
||||
|
||||
logger.LogInformation($"Агент подключился: {agentConnection.AgentInstance!.InstanceName}. Идентификатор соединения: {agentConnection.ConnectionId}");
|
||||
}
|
||||
|
||||
private void AgentConnection_Disconnected(AgentConnection agentConnection)
|
||||
{
|
||||
Connections.TryRemove(agentConnection.ConnectionId, out _);
|
||||
lock (_connections)
|
||||
_connections.Remove(agentConnection);
|
||||
|
||||
agentConnection.AgentConnected -= AgentConnection_Connected;
|
||||
agentConnection.AgentDisconnected -= AgentConnection_Disconnected;
|
||||
agentConnection.SubscribedForCommands -= AgentConnection_SubscribedForCommands;
|
||||
|
||||
var commandsWatcher = _commandsSubscribers.FirstOrDefault(c => c.Value == agentConnection.ConnectionId);
|
||||
if (commandsWatcher.Key != Guid.Empty)
|
||||
_commandsSubscribers.TryRemove(commandsWatcher.Key, out _);
|
||||
|
||||
logger.LogInformation($"Агент отключиться: {agentConnection.AgentInstance!.InstanceName}");
|
||||
}
|
||||
|
||||
private void AgentConnection_SubscribedForCommands(AgentConnection agentConnection)
|
||||
{
|
||||
_commandsSubscribers.TryAdd(agentConnection.AgentInstance!.Id, agentConnection.ConnectionId);
|
||||
logger.LogInformation($"Агент отключился: {agentConnection.AgentInstance!.InstanceName}. Идентификатор соединения: {agentConnection.ConnectionId}");
|
||||
}
|
||||
|
||||
public bool IsConnected(Guid agentId)
|
||||
=> _commandsSubscribers.ContainsKey(agentId);
|
||||
=> GetAgentConnection(agentId) != null;
|
||||
|
||||
public AgentConnection? GetCommandsSubscriberConnection(Guid id)
|
||||
public AgentConnection? GetAgentConnection(Guid agentId)
|
||||
{
|
||||
if (_commandsSubscribers.TryGetValue(id, out var connectionId) &&
|
||||
Connections.TryGetValue(connectionId, out var agentConnection))
|
||||
return agentConnection;
|
||||
AgentConnection? agentConnection;
|
||||
|
||||
lock (_connections)
|
||||
agentConnection = _connections.FirstOrDefault(c => c.AgentInstance!.MainConnection && c.AgentInstance.Id == agentId);
|
||||
|
||||
return null;
|
||||
return agentConnection;
|
||||
}
|
||||
|
||||
public List<AgentConnection> GetCommandSubscribers(List<Agent> agents)
|
||||
=> agents.Select(c => GetCommandsSubscriberConnection(c.Id)).Where(c => c != null).ToList()!;
|
||||
public List<AgentConnection> GetAgentsConnections(List<Agent> agents)
|
||||
=> agents.Select(c => GetAgentConnection(c.Id)).Where(c => c != null).ToList()!;
|
||||
|
||||
public List<Agent> GetConnectedAgents(List<Agent> agents)
|
||||
=> agents.Where(c => _commandsSubscribers.ContainsKey(c.Id)).ToList();
|
||||
|
||||
public async Task UpdateTechLogSeances(List<Agent> agents, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var connection in agents.Select(agent => GetCommandsSubscriberConnection(agent.Id)).OfType<AgentConnection>())
|
||||
{
|
||||
try
|
||||
{
|
||||
await connection.RequestTechLogSeancesUpdating(cancellationToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
=> agents.Where(c => IsConnected(c.Id)).ToList();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using OnecMonitor.Common.Models.MaintenanceTasks;
|
||||
using OnecMonitor.Server.Models.MaintenanceTasks;
|
||||
|
||||
namespace OnecMonitor.Server.ViewModels.MaintenanceTasks;
|
||||
|
@ -1,7 +1,8 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using OnecMonitor.Server.Extensions;
|
||||
using OnecMonitor.Common.Extensions;
|
||||
using OnecMonitor.Common.Models.MaintenanceTasks;
|
||||
using OnecMonitor.Server.Helpers;
|
||||
using OnecMonitor.Server.Models;
|
||||
using OnecMonitor.Server.Models.MaintenanceTasks;
|
||||
|
@ -3,5 +3,8 @@ namespace OnecMonitor.Server.ViewModels.MaintenanceTasks;
|
||||
public class MaintenanceTaskListItemViewModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public DateTime StartDateTime { get; set; }
|
||||
public DateTime FinishDateTime { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public bool IsFaulted { get; set; } = false;
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@using NuGet.Protocol
|
||||
@using OnecMonitor.Server.Extensions
|
||||
@using OnecMonitor.Server.Helpers
|
||||
@using OnecMonitor.Server.Models.MaintenanceTasks
|
||||
@using OnecMonitor.Common.Extensions
|
||||
@using OnecMonitor.Common.Models.MaintenanceTasks
|
||||
@using OnecMonitor.Server.ViewModels
|
||||
@model OnecMonitor.Server.ViewModels.MaintenanceTasks.MaintenanceTaskEditViewModel
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@using OnecMonitor.Server.Extensions
|
||||
@using OnecMonitor.Server.Models.MaintenanceTasks
|
||||
@using OnecMonitor.Server.ViewModels
|
||||
@using OnecMonitor.Common.Models.MaintenanceTasks
|
||||
@model OnecMonitor.Server.ViewModels.MaintenanceTasks.MaintenanceStepViewModel
|
||||
|
||||
<form method="post" style="height: 100%">
|
||||
|
@ -1,3 +1,4 @@
|
||||
@using System.Globalization
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@using OnecMonitor.Server.ViewModels
|
||||
@using OnecMonitor.Server.ViewModels.Agents;
|
||||
@ -18,17 +19,44 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col">Описание</th>
|
||||
<th class="col">Запущена</th>
|
||||
<th class="col">Есть ошибки</th>
|
||||
<th class="col">Завершена</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
var started = item.StartDateTime != DateTime.MinValue;
|
||||
var finished = item.FinishDateTime != DateTime.MinValue;
|
||||
|
||||
<tr>
|
||||
<td>@item.Description</td>
|
||||
<td>@(started ? item.StartDateTime.ToString(CultureInfo.InvariantCulture) : "")</td>
|
||||
@if (item.IsFaulted)
|
||||
{
|
||||
<td class="text-danger">@item.IsFaulted</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td class="text-success">@item.IsFaulted</td>
|
||||
}
|
||||
<td>@(finished ? item.FinishDateTime.ToString(CultureInfo.InvariantCulture) : "")</td>
|
||||
<td class="text-center">
|
||||
<a class="btn btn-outline-warning btn-sm" asp-action="Edit" asp-route-id="@item.Id">Изменить</a>
|
||||
<a class="btn btn-outline-danger btn-sm" onclick="OM.openDeleteDialog('@item.Id', '@item.Description')">Удалить</a>
|
||||
@if (started)
|
||||
{
|
||||
<a class="btn btn-outline-info btn-sm" asp-action="Log" asp-route-id="@item.Id">Лог</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="btn btn-outline-primary btn-sm" asp-action="Start" asp-route-id="@item.Id">Запустить</a>
|
||||
<a class="btn btn-outline-warning btn-sm" asp-action="Edit" asp-route-id="@item.Id">Изменить</a>
|
||||
}
|
||||
@if (!started || finished)
|
||||
{
|
||||
<a class="btn btn-outline-danger btn-sm" onclick="OM.openDeleteDialog('@item.Id', '@item.Description')">Удалить</a>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
@ -1,7 +1,4 @@
|
||||
@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,11 +24,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||
<PackageReference Include="ConfigureAwait.Fody" Version="3.3.2" PrivateAssets="All" />
|
||||
<PackageReference Include="Fody" Version="6.9.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.67.0" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.67.0" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Web" Version="2.67.0" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user