인증서 생성기 화면 및 관련 작업

This commit is contained in:
2026-02-23 00:07:22 +09:00
parent 4e006a5a5f
commit 4ea351946a
53 changed files with 1437 additions and 656 deletions

View File

@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc;
using Npgsql;
using OpcPks.Core.Data;
using OpcPks.Core.Services;
using OpcPks.Core.Models; // CertRequestModel 참조 추가
using System;
using System.Collections.Generic;
using System.Linq;
@@ -10,13 +11,18 @@ using System.Threading.Tasks;
namespace OpcPks.Web.Controllers;
[Route("Engineering")] // 🚨 경로를 명시적으로 고정
[Route("Engineering")]
public class EngineeringController : Controller
{
[HttpGet("TagExplorer")] // Engineering/TagExplorer
// 하니웰 데이터 및 PKI 경로 고정
private readonly string _basePath = "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/Data";
#region [ : ]
[HttpGet("TagExplorer")]
public IActionResult TagExplorer() => View();
[HttpGet("Admin")] // Engineering/Admin
[HttpGet("Admin")]
public IActionResult Admin() => View();
[HttpPost("SearchByFilter")]
@@ -100,34 +106,20 @@ public class EngineeringController : Controller
[HttpPost("RunCrawler")]
public async Task<IActionResult> RunCrawler()
{
Console.WriteLine("\n[API] === RunCrawler 요청 수신됨 ===");
try
{
try {
var sessionManager = new OpcSessionManager();
var session = await sessionManager.GetSessionAsync();
if (session == null || !session.Connected)
{
Console.WriteLine("❌ [API] 세션 연결 실패!");
return BadRequest(new { message = "하니웰 서버 연결 실패." });
}
var crawler = new HoneywellCrawler(session);
string csvPath = @"/home/pacer/projects/OpcPksPlatform/OpcPks.Core/Data/Honeywell_FullMap.csv";
string csvPath = Path.Combine(_basePath, "Honeywell_FullMap.csv");
string dir = Path.GetDirectoryName(csvPath);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
await crawler.RunAsync("ns=1;s=$assetmodel", csvPath);
Console.WriteLine("✅ [API] 모든 탐사 공정 완료!");
return Ok(new { message = "탐사 및 CSV 생성 완료!" });
}
catch (Exception ex)
{
Console.WriteLine($"❌ [API] 치명적 오류: {ex.Message}");
return BadRequest(new { message = ex.Message });
}
catch (Exception ex) { return BadRequest(new { message = ex.Message }); }
}
[HttpPost("ImportCsv")]
@@ -136,10 +128,11 @@ public class EngineeringController : Controller
try {
using var conn = new NpgsqlConnection(DbConfig.ConnectionString);
await conn.OpenAsync();
var sql = @"TRUNCATE raw_node_map;
COPY raw_node_map(level, node_class, name, node_id)
FROM '/home/pacer/projects/OpcPksPlatform/OpcPks.Core/Data/Honeywell_FullMap.csv'
DELIMITER ',' CSV HEADER;";
string csvPath = Path.Combine(_basePath, "Honeywell_FullMap.csv");
var sql = $@"TRUNCATE raw_node_map;
COPY raw_node_map(level, node_class, name, node_id)
FROM '{csvPath}'
DELIMITER ',' CSV HEADER;";
using var cmd = new NpgsqlCommand(sql, conn);
await cmd.ExecuteNonQueryAsync();
return Ok(new { message = "DB 동기화 완료" });
@@ -147,6 +140,48 @@ public class EngineeringController : Controller
catch (Exception ex) { return BadRequest(ex.Message); }
}
#endregion
#region [ : ]
[HttpGet("CertManager")]
public IActionResult CertManager()
{
string pfxPath = Path.Combine(_basePath, "pki/own/private/OpcTestClient.pfx");
// 파일 존재 여부를 ViewData에 담아 보냅니다.
ViewBag.IsCertExists = System.IO.File.Exists(pfxPath);
ViewBag.SuccessMsg = TempData["Success"];
ViewBag.ErrorMsg = TempData["Error"];
return View(new CertRequestModel());
}
[HttpPost("GenerateCertificate")]
public async Task<IActionResult> GenerateCertificate(CertRequestModel model)
{
try
{
var generator = new CertificateGenerator(_basePath);
var success = await generator.CreateHoneywellCertificateAsync(model);
if (success) {
TempData["Success"] = "하니웰 FTE 대응 인증서가 생성 및 백업되었습니다.";
return RedirectToAction("CertManager");
}
TempData["Error"] = "인증서 생성 중 오류가 발생했습니다.";
return RedirectToAction("CertManager");
}
catch (Exception ex)
{
TempData["Error"] = ex.Message;
return RedirectToAction("CertManager");
}
}
#endregion
public class SearchRequest { public string TagTerm { get; set; } public List<string> Suffixes { get; set; } }
public class TagRegistrationRequest { public string TagName { get; set; } public string NodeId { get; set; } public string DataType { get; set; } }
}

View File

@@ -1,24 +1,107 @@
<div class="card border-danger">
<div class="card-header bg-danger text-white">System Engineering - Discovery Mode</div>
<div class="card-body">
<h5>1. 하니웰 자산 모델 탐사 (Crawler)</h5>
<p class="text-muted">서버의 모든 노드를 훑어 CSV 파일을 생성합니다. (시간이 오래 걸릴 수 있음)</p>
<button id="btnRunCrawler" class="btn btn-danger" onclick="runCrawler()">🚀 탐사 및 CSV 생성 시작</button>
<hr>
@{
ViewData["Title"] = "System Admin";
}
<h5>2. DB 동기화 (CSV to Database)</h5>
<p class="text-muted">생성된 Honeywell_FullMap.csv 파일을 읽어 DB에 일괄 저장합니다.</p>
<button id="btnImportCsv" class="btn btn-warning" onclick="importCsvToDb()">📥 CSV 데이터를 DB로 가져오기</button>
<div class="container mt-4">
<div class="card border-danger shadow">
<div class="card-header bg-danger text-white">
<h4 class="mb-0">🛠️ System Engineering - Discovery Mode</h4>
</div>
<div class="card-body">
<section class="mb-5">
<h5>1. 하니웰 Asset 모델 탐사 (Crawler)</h5>
<p class="text-muted">서버의 모든 노드를 훑어 <code>Honeywell_FullMap.csv</code> 파일을 생성합니다. <br>
<small class="text-danger">* 주의: 노드 수가 많을 경우 하니웰 서버와 네트워크에 부하가 발생할 수 있습니다.</small></p>
<button id="btnRunCrawler" class="btn btn-danger btn-lg" onclick="runCrawler()">
<span id="spinnerCrawler" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
🚀 탐사 및 CSV 생성 시작
</button>
</section>
<hr>
<section class="mt-4">
<h5>2. DB 동기화 (CSV to Database)</h5>
<p class="text-muted">생성된 CSV 파일을 읽어 PostgreSQL <code>raw_node_map</code> 테이블에 일괄 저장합니다.</p>
<button id="btnImportCsv" class="btn btn-warning btn-lg" onclick="importCsvToDb()">
<span id="spinnerImport" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
📥 CSV 데이터를 DB로 가져오기
</button>
</section>
</div>
<div class="card-footer bg-light">
<div id="statusMessage" class="fw-bold"></div>
</div>
</div>
</div>
@section Scripts {
<script>
function runCrawler() {
if(!confirm("전체 노드 탐사를 시작하시겠습니까? 하니웰 서버에 부하가 갈 수 있습니다.")) return;
// API 호출: /Engineering/RunCrawler
async function runCrawler() {
const btn = document.getElementById('btnRunCrawler');
const spinner = document.getElementById('spinnerCrawler');
const status = document.getElementById('statusMessage');
if (!confirm("전체 노드 탐사를 시작하시겠습니까? (ns=1;s=$assetmodel 기준)")) return;
try {
// 버튼 비활성화 및 로딩 시작
btn.disabled = true;
spinner.classList.remove('d-none');
status.innerText = "⏳ 하니웰 서버 탐사 중... 잠시만 기다려 주세요.";
status.className = "text-primary fw-bold";
const response = await fetch('/Engineering/RunCrawler', { method: 'POST' });
const result = await response.json();
if (response.ok) {
alert("✅ 성공: " + result.message);
status.innerText = "✅ 탐사 완료: " + result.message;
status.className = "text-success fw-bold";
} else {
throw new Error(result.message || "탐사 실패");
}
} catch (error) {
alert("❌ 에러 발생: " + error.message);
status.innerText = "❌ 오류: " + error.message;
status.className = "text-danger fw-bold";
} finally {
btn.disabled = false;
spinner.classList.add('d-none');
}
}
function importCsvToDb() {
// API 호출: /Engineering/ImportCsv
async function importCsvToDb() {
const btn = document.getElementById('btnImportCsv');
const spinner = document.getElementById('spinnerImport');
const status = document.getElementById('statusMessage');
if (!confirm("CSV 데이터를 DB에 덮어씌우시겠습니까? (기존 데이터는 삭제됩니다.)")) return;
try {
btn.disabled = true;
spinner.classList.remove('d-none');
status.innerText = "⏳ DB 동기화 작업 중...";
const response = await fetch('/Engineering/ImportCsv', { method: 'POST' });
const result = await response.json();
if (response.ok) {
alert("✅ 성공: " + result.message);
status.innerText = "✅ DB 동기화 완료!";
status.className = "text-success fw-bold";
} else {
throw new Error(result.message || "동기화 실패");
}
} catch (error) {
alert("❌ 에러 발생: " + error.message);
status.innerText = "❌ 오류: " + error.message;
status.className = "text-danger fw-bold";
} finally {
btn.disabled = false;
spinner.classList.add('d-none');
}
}
</script>
</script>
}

View File

@@ -0,0 +1,128 @@
@model OpcPks.Core.Models.CertRequestModel
<div class="container mt-4">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h4>🛡️ 하니웰 Experion 전용 인증서 생성기</h4>
</div>
<div class="card-body">
<form asp-action="GenerateCertificate" method="post">
<div class="row mb-4">
<div class="col-md-6">
<label class="form-label fw-bold">시스템 구성</label>
<select asp-for="IsRedundant" class="form-select" id="systemType">
<option value="false">Standalone (단일 서버)</option>
<option value="true">Redundant (이중화 서버)</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label fw-bold">어플리케이션 이름</label>
<input asp-for="ApplicationName" class="form-control" placeholder="OpcPksClient" />
</div>
</div>
<hr />
<h5 class="text-secondary"><span class="badge bg-info">Primary</span> 서버 정보</h5>
<div class="row mb-3">
<div class="col-md-4">
<label class="form-label">Host Name</label>
<input asp-for="PrimaryHostName" class="form-control" placeholder="예: HONPKS" />
</div>
<div class="col-md-4">
<label class="form-label text-warning">IP Address (Yellow)</label>
<input asp-for="PrimaryIpA" class="form-control" placeholder="192.168.0.20" />
</div>
<div class="col-md-4">
<label class="form-label text-success">IP Address (Green)</label>
<input asp-for="PrimaryIpB" class="form-control" placeholder="192.168.1.20" />
</div>
</div>
<h5 class="text-secondary mt-4"><span class="badge bg-secondary" id="secondaryBadge">Secondary</span> 서버 정보</h5>
<div class="row mb-3" id="secondaryFields">
<div class="col-md-4">
<label class="form-label">Host Name</label>
<input asp-for="SecondaryHostName" class="form-control sec-input" placeholder="예: HONPKS" />
</div>
<div class="col-md-4">
<label class="form-label text-warning">IP Address (Yellow)</label>
<input asp-for="SecondaryIpA" class="form-control sec-input" placeholder="192.168.0.21" />
</div>
<div class="col-md-4">
<label class="form-label text-success">IP Address (Green)</label>
<input asp-for="SecondaryIpB" class="form-control sec-input" placeholder="192.168.1.21" />
</div>
</div>
<div class="card bg-light border-warning mt-5">
<div class="card-body">
@if (ViewBag.IsCertExists == true)
{
<div class="alert alert-warning d-flex align-items-center">
<span class="fs-4 me-3">⚠️</span>
<div>
<strong>기존 인증서가 이미 존재합니다!</strong><br />
새로 생성 시 기존 서버와의 통신 신뢰관계(Trust)가 깨질 수 있습니다.
</div>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="chkUnlock" onchange="toggleCertBtn()">
<label class="form-check-label text-danger fw-bold" for="chkUnlock">
[위험 인지] 기존 인증서를 무시하고 새로 생성하는 것에 동의합니다.
</label>
</div>
<button type="submit" id="btnGenerate" class="btn btn-danger btn-lg w-100" disabled>
🔒 인증서가 이미 존재하여 잠겨있습니다
</button>
}
else
{
<button type="submit" id="btnGenerate" class="btn btn-primary btn-lg w-100">
🚀 인증서 생성 및 자동 적용 시작
</button>
}
</div>
</div>
</form>
</div>
</div>
</div>
@section Scripts {
<script>
$(document).ready(function () {
// 1. 이중화 선택 로직
$('#systemType').change(function () {
const isRedundant = $(this).val() === 'true';
$('.sec-input').prop('disabled', !isRedundant);
if(isRedundant) {
$('#secondaryBadge').removeClass('bg-secondary').addClass('bg-info');
} else {
$('#secondaryBadge').removeClass('bg-info').addClass('bg-secondary');
}
});
$('#systemType').trigger('change');
});
// 2. 버튼 잠금 해제 로직
function toggleCertBtn() {
const isChecked = document.getElementById('chkUnlock').checked;
const btn = document.getElementById('btnGenerate');
if(isChecked) {
btn.disabled = false;
btn.innerText = "🔥 인증서 새로 생성 (강제 실행)";
btn.classList.replace('btn-danger', 'btn-warning');
} else {
btn.disabled = true;
btn.innerText = "🔒 인증서가 이미 존재하여 잠겨있습니다";
btn.classList.replace('btn-warning', 'btn-danger');
}
}
</script>
}

View File

@@ -12,7 +12,7 @@
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">OpcPks.Web</a>
<a class="navbar-brand fw-bold" asp-area="" asp-controller="Home" asp-action="Index">🚀 OpcPks Platform</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
@@ -22,7 +22,24 @@
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<span class="nav-link text-muted">|</span>
</li>
<li class="nav-item">
<a class="nav-link text-primary" asp-area="" asp-controller="Engineering" asp-action="TagExplorer">🔍 Tag Explorer</a>
</li>
<li class="nav-item">
<a class="nav-link text-success" asp-area="" asp-controller="Engineering" asp-action="Admin">⚙️ DB Admin</a>
</li>
<li class="nav-item">
<a class="nav-link text-danger" asp-area="" asp-controller="Engineering" asp-action="CertManager">🛡️ Cert Manager</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
</ul>
@@ -30,7 +47,23 @@
</div>
</nav>
</header>
<div class="container">
@if (TempData["Success"] != null)
{
<div class="alert alert-success alert-dismissible fade show" role="alert">
<strong>성공!</strong> @TempData["Success"]
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
}
@if (TempData["Error"] != null)
{
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<strong>오류!</strong> @TempData["Error"]
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
}
<main role="main" class="pb-3">
@RenderBody()
</main>
@@ -46,4 +79,4 @@
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
</html>

View File

@@ -13,10 +13,10 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("OpcPks.Web")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+171aaf6115cde8e9e930d14886fafa5fe4c1e4c0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+4e006a5a5f65f597fafaa3777b8a1073944eada2")]
[assembly: System.Reflection.AssemblyProductAttribute("OpcPks.Web")]
[assembly: System.Reflection.AssemblyTitleAttribute("OpcPks.Web")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.
// MSBuild WriteCodeFragment 클래스에서 생성되었습니다.

View File

@@ -1 +1 @@
69146d785d05a31782563e3020fd0b70195b5ca5af2eb9bc318d486b90f84fe9
e1494e87c92c732759b5281464380da6aa825fc12aba342324181898b9f0d33d

View File

@@ -22,6 +22,10 @@ build_property._RazorSourceGeneratorDebug =
build_metadata.AdditionalFiles.TargetPath = Vmlld3MvRW5naW5lZXJpbmcvQWRtaW4uY3NodG1s
build_metadata.AdditionalFiles.CssScope =
[/home/pacer/projects/OpcPksPlatform/OpcPks.Web/Views/Engineering/CertManager.cshtml]
build_metadata.AdditionalFiles.TargetPath = Vmlld3MvRW5naW5lZXJpbmcvQ2VydE1hbmFnZXIuY3NodG1s
build_metadata.AdditionalFiles.CssScope =
[/home/pacer/projects/OpcPksPlatform/OpcPks.Web/Views/Engineering/TagExplorer.cshtml]
build_metadata.AdditionalFiles.TargetPath = Vmlld3MvRW5naW5lZXJpbmcvVGFnRXhwbG9yZXIuY3NodG1s
build_metadata.AdditionalFiles.CssScope =

View File

@@ -1 +1 @@
211409f9c66f4d86deef10e146a286bf43dcff595588425d60ad90ee2a7fd32f
da88b9ec6f30d3a67ed38dde28b0fbcd513cedae3a9742b74a5f06636cf1a15e

View File

@@ -1 +1 @@
ef34cdd3c41169ab6b411993059073056d84145d36a38b4aea950172aff7d741
6467bb07a49b01c95068173444a1866c77670de41eed0f2a920478da9d4c65bc

View File

@@ -10,11 +10,11 @@
"projectUniqueName": "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/OpcPks.Core.csproj",
"projectName": "OpcPks.Core",
"projectPath": "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/OpcPks.Core.csproj",
"packagesPath": "/home/pacer/.nuget/packages/",
"packagesPath": "/root/.nuget/packages/",
"outputPath": "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/obj/",
"projectStyle": "PackageReference",
"configFilePaths": [
"/home/pacer/.nuget/NuGet/NuGet.Config"
"/root/.nuget/NuGet/NuGet.Config"
],
"originalTargetFrameworks": [
"net8.0"
@@ -73,11 +73,11 @@
"projectUniqueName": "/home/pacer/projects/OpcPksPlatform/OpcPks.Web/OpcPks.Web.csproj",
"projectName": "OpcPks.Web",
"projectPath": "/home/pacer/projects/OpcPksPlatform/OpcPks.Web/OpcPks.Web.csproj",
"packagesPath": "/home/pacer/.nuget/packages/",
"packagesPath": "/root/.nuget/packages/",
"outputPath": "/home/pacer/projects/OpcPksPlatform/OpcPks.Web/obj/",
"projectStyle": "PackageReference",
"configFilePaths": [
"/home/pacer/.nuget/NuGet/NuGet.Config"
"/root/.nuget/NuGet/NuGet.Config"
],
"originalTargetFrameworks": [
"net8.0"

View File

@@ -4,12 +4,12 @@
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/pacer/.nuget/packages/</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/pacer/.nuget/packages/</NuGetPackageFolders>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/root/.nuget/packages/</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/root/.nuget/packages/</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.8.1</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="/home/pacer/.nuget/packages/" />
<SourceRoot Include="/root/.nuget/packages/" />
</ItemGroup>
</Project>

View File

@@ -574,7 +574,7 @@
]
},
"packageFolders": {
"/home/pacer/.nuget/packages/": {}
"/root/.nuget/packages/": {}
},
"project": {
"version": "1.0.0",
@@ -582,11 +582,11 @@
"projectUniqueName": "/home/pacer/projects/OpcPksPlatform/OpcPks.Web/OpcPks.Web.csproj",
"projectName": "OpcPks.Web",
"projectPath": "/home/pacer/projects/OpcPksPlatform/OpcPks.Web/OpcPks.Web.csproj",
"packagesPath": "/home/pacer/.nuget/packages/",
"packagesPath": "/root/.nuget/packages/",
"outputPath": "/home/pacer/projects/OpcPksPlatform/OpcPks.Web/obj/",
"projectStyle": "PackageReference",
"configFilePaths": [
"/home/pacer/.nuget/NuGet/NuGet.Config"
"/root/.nuget/NuGet/NuGet.Config"
],
"originalTargetFrameworks": [
"net8.0"

View File

@@ -1,19 +1,19 @@
{
"version": 2,
"dgSpecHash": "Lhk78ivUpJSYUca9AozBeOWL1MSXwG/J03crkR8ohPXr0Jr13fI6SjcPbbT0IMW8j7gHacAchV4I/6FtcYipBA==",
"dgSpecHash": "1ODJAJibZeCWjDB9k93/7u2tzIkxUKCpt4LpJwtoyk9SPN+V9Z00ioG5BCZSRPJv0f/i3y9/JoZYPrLxXjCawQ==",
"success": true,
"projectFilePath": "/home/pacer/projects/OpcPksPlatform/OpcPks.Web/OpcPks.Web.csproj",
"expectedPackageFiles": [
"/home/pacer/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/8.0.1/microsoft.extensions.dependencyinjection.abstractions.8.0.1.nupkg.sha512",
"/home/pacer/.nuget/packages/microsoft.extensions.logging.abstractions/8.0.1/microsoft.extensions.logging.abstractions.8.0.1.nupkg.sha512",
"/home/pacer/.nuget/packages/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg.sha512",
"/home/pacer/.nuget/packages/npgsql/8.0.4/npgsql.8.0.4.nupkg.sha512",
"/home/pacer/.nuget/packages/opcfoundation.netstandard.opc.ua.client/1.5.374.78/opcfoundation.netstandard.opc.ua.client.1.5.374.78.nupkg.sha512",
"/home/pacer/.nuget/packages/opcfoundation.netstandard.opc.ua.configuration/1.5.374.78/opcfoundation.netstandard.opc.ua.configuration.1.5.374.78.nupkg.sha512",
"/home/pacer/.nuget/packages/opcfoundation.netstandard.opc.ua.core/1.5.374.78/opcfoundation.netstandard.opc.ua.core.1.5.374.78.nupkg.sha512",
"/home/pacer/.nuget/packages/opcfoundation.netstandard.opc.ua.security.certificates/1.5.374.78/opcfoundation.netstandard.opc.ua.security.certificates.1.5.374.78.nupkg.sha512",
"/home/pacer/.nuget/packages/system.formats.asn1/8.0.1/system.formats.asn1.8.0.1.nupkg.sha512",
"/home/pacer/.nuget/packages/system.security.cryptography.cng/5.0.0/system.security.cryptography.cng.5.0.0.nupkg.sha512"
"/root/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/8.0.1/microsoft.extensions.dependencyinjection.abstractions.8.0.1.nupkg.sha512",
"/root/.nuget/packages/microsoft.extensions.logging.abstractions/8.0.1/microsoft.extensions.logging.abstractions.8.0.1.nupkg.sha512",
"/root/.nuget/packages/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg.sha512",
"/root/.nuget/packages/npgsql/8.0.4/npgsql.8.0.4.nupkg.sha512",
"/root/.nuget/packages/opcfoundation.netstandard.opc.ua.client/1.5.374.78/opcfoundation.netstandard.opc.ua.client.1.5.374.78.nupkg.sha512",
"/root/.nuget/packages/opcfoundation.netstandard.opc.ua.configuration/1.5.374.78/opcfoundation.netstandard.opc.ua.configuration.1.5.374.78.nupkg.sha512",
"/root/.nuget/packages/opcfoundation.netstandard.opc.ua.core/1.5.374.78/opcfoundation.netstandard.opc.ua.core.1.5.374.78.nupkg.sha512",
"/root/.nuget/packages/opcfoundation.netstandard.opc.ua.security.certificates/1.5.374.78/opcfoundation.netstandard.opc.ua.security.certificates.1.5.374.78.nupkg.sha512",
"/root/.nuget/packages/system.formats.asn1/8.0.1/system.formats.asn1.8.0.1.nupkg.sha512",
"/root/.nuget/packages/system.security.cryptography.cng/5.0.0/system.security.cryptography.cng.5.0.0.nupkg.sha512"
],
"logs": []
}