Compare commits

..

17 Commits

Author SHA1 Message Date
astankovmi 2883b537ab Проверки и отправка в Visor
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 11s
2026-07-13 15:11:05 +03:00
astankovmi 6d61bce884 Добавил wizard
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 12s
2026-07-08 16:19:15 +03:00
astankovmi fd6506af7b Добавил добавление объекта
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 12s
2026-07-03 18:35:09 +03:00
astankovmi 5b14466d24 UPD Visor
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 12s
2026-06-30 17:43:10 +03:00
astankovmi 38a825b50b UDP Visor
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 12s
2026-06-30 16:26:31 +03:00
astankovmi 7aa990a12b Добавил BLAZORIES.Переписать все на него.
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 37s
2026-06-23 19:28:04 +03:00
astankovmi 90cbc7c506 Add Face
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
2026-06-23 16:33:23 +03:00
astankovmi 794fc621ff upd
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 10s
2026-06-23 16:26:57 +03:00
astankovmi d426ab1e78 upd
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 10s
2026-06-16 17:05:43 +03:00
astankovmi 6ddc6be910 OK
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
2026-06-09 02:54:25 +03:00
astankovmi f3c4c55c55 ok
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 9s
2026-06-09 02:52:44 +03:00
astankovmi c94e07d6c0 ok 2026-06-09 02:52:44 +03:00
astankovmi eeaf87a07e upd 2026-06-09 02:52:44 +03:00
astankovmi 04b5303dac ok 2026-06-09 02:52:44 +03:00
astankovmi 73a4e0b5bb OK 2026-06-09 02:52:44 +03:00
astankovmi 57aebb7b1c OK 2026-06-09 02:52:16 +03:00
astankovmi 76d090b744 upd
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
2026-06-09 02:22:51 +03:00
104 changed files with 80747 additions and 254 deletions
+9 -22
View File
@@ -6,28 +6,15 @@ namespace DahuaIPCRename
{ {
public static Dictionary<string, string> listIPC = new Dictionary<string, string> public static Dictionary<string, string> listIPC = new Dictionary<string, string>
{ {
{"172.16.54.18","Ворота 35"}, {"172.22.10.122","Этаж 2|Пом. 247"},
{"172.16.54.20","Ворота 36"}, {"172.22.10.123","Этаж 2|Пом. 248-4"},
{"172.16.54.21","Ворота 31"}, {"172.22.10.124","Этаж 2|Пом. 248-5"},
{"172.16.54.22","Ворота 32"}, {"172.22.10.125","Этаж 2|Пом. 248-2"},
{"172.16.54.23","Ворота 33"}, {"172.22.10.126","Этаж 2|Пом. 248-6"},
{"172.16.54.24","Ворота 34"}, {"172.22.10.127","Этаж 2|Пом. 248-3"},
{"172.16.54.28","Ворота 16"}, {"172.22.10.128","Этаж 2|Пом. 273-1"},
{"172.16.54.29","Ворота 26"}, {"172.22.10.129","Этаж 2|Пом. 268-1"},
{"172.16.54.30","Ворота 20"}, {"172.22.10.130","Этаж 2|Пом. 273-2"}
{"172.16.54.31","Ворота 22"},
{"172.16.54.32","Ворота 18"},
{"172.16.54.33","Ворота 21"},
{"172.16.54.34","Ворота 24"},
{"172.16.54.35","Ворота 27"},
{"172.16.54.36","Ворота 29"},
{"172.16.54.37","Ворота 25"},
{"172.16.54.38","Ворота 23"},
{"172.16.54.39","Ворота 15"},
{"172.16.54.40","Ворота 17"},
{"172.16.54.41","Ворота 37"},
{"172.16.54.42","Ворота 19"},
{"172.16.54.43","Ворота 28"}
}; };
static async Task Main(string[] args) static async Task Main(string[] args)
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Emgu.CV" Version="4.13.0.5924" />
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.13.0.5924" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.11" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.9" />
<PackageReference Include="System.Drawing.Common" Version="10.0.9" />
</ItemGroup>
<ItemGroup>
<None Update="haarcascade_frontalface_default.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+7
View File
@@ -0,0 +1,7 @@
using Microsoft.AspNetCore.SignalR;
namespace FaceRecognitionApp.Hubs;
public class FaceHub : Hub
{
}
+18
View File
@@ -0,0 +1,18 @@
namespace FaceRecognitionApp.Models;
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Department { get; set; }
public byte[] FaceDescriptor { get; set; } = Array.Empty<byte>();
public byte[]? Photo { get; set; }
}
public class DetectedFace
{
public byte[] ImageData { get; set; } = Array.Empty<byte>();
public bool IsRecognized { get; set; }
public Employee? MatchedEmployee { get; set; }
public double Confidence { get; set; }
}
+58
View File
@@ -0,0 +1,58 @@
using FaceRecognitionApp.Hubs;
using FaceRecognitionApp.Services;
using Microsoft.AspNetCore.SignalR;
var builder = WebApplication.CreateBuilder(args);
// Регистрация сервисов
builder.Services.AddSingleton<DatabaseService>();
builder.Services.AddSingleton<FaceRecognitionService>();
builder.Services.AddSignalR();
var app = builder.Build();
// 1. Включаем поиск файлов по умолчанию (index.html, default.html и т.д.)
app.UseDefaultFiles();
// 2. Включаем раздачу статических файлов (css, js, картинки)
app.UseStaticFiles();
// 3. Подключаем SignalR хаб
app.MapHub<FaceHub>("/faceHub");
// Инициализация камеры и распознавания
var db = app.Services.GetRequiredService<DatabaseService>();
var faceService = app.Services.GetRequiredService<FaceRecognitionService>();
// URL камеры (замените на свой или используйте тестовый видеофайл)
var cameraUrl = Environment.GetEnvironmentVariable("CAMERA_URL") ?? "rtsp://admin:4NUDZhJ7@172.22.10.101:554/cam/realmonitor?channel=1&subtype=1";
try
{
Console.WriteLine($"Connecting to camera: {cameraUrl}...");
var camera = new CameraService(cameraUrl, faceService, async (face) => {
var hub = app.Services.GetRequiredService<IHubContext<FaceHub>>();
await hub.Clients.All.SendAsync("NewFace", new
{
image = Convert.ToBase64String(face.ImageData),
isRecognized = face.IsRecognized,
name = face.MatchedEmployee?.Name ?? "Unknown"
});
});
camera.Start();
Console.WriteLine("✅ Camera service started.");
// Корректное завершение работы камеры при остановке приложения
app.Lifetime.ApplicationStopping.Register(() => {
Console.WriteLine("Shutting down camera...");
camera.Dispose();
});
}
catch (Exception ex)
{
Console.WriteLine($"⚠️ Camera Error: {ex.Message}");
Console.WriteLine("Application will run without camera monitoring.");
}
Console.WriteLine("🚀 Application is ready. Open http://localhost:5290 in your browser.");
app.Run();
@@ -0,0 +1,29 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:43065",
"sslPort": 0
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5290",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -0,0 +1,144 @@
using Emgu.CV;
using FaceRecognitionApp.Models;
namespace FaceRecognitionApp.Services;
public class CameraService : IDisposable
{
private VideoCapture? _capture;
private readonly FaceRecognitionService _recognition;
private readonly Action<DetectedFace> _onDetected;
private CancellationTokenSource? _cts;
private Task? _task;
private DateTime _lastTime = DateTime.MinValue;
private bool _isDisposed = false;
public CameraService(string url, FaceRecognitionService recognition, Action<DetectedFace> onDetected)
{
_recognition = recognition;
_onDetected = onDetected;
Console.WriteLine($"Initializing camera with URL: {url}");
_capture = new VideoCapture(url);
// Даем камере время на инициализацию потока
Thread.Sleep(1000);
if (!_capture.IsOpened)
throw new Exception("Camera failed to open. Check URL and network connection.");
Console.WriteLine("Camera hardware initialized.");
}
public void Start()
{
if (_isDisposed) return;
_cts = new CancellationTokenSource();
_task = Task.Run(() => Loop(_cts.Token));
Console.WriteLine("Camera processing loop started.");
}
private async Task Loop(CancellationToken token)
{
Console.WriteLine("🎥 Frame processing loop active...");
while (!token.IsCancellationRequested)
{
try
{
using var frame = new Mat();
// Читаем кадр с таймаутом
if (_capture!.Read(frame))
{
if (!frame.IsEmpty && frame.Width > 0 && frame.Height > 0)
{
var face = _recognition.ProcessFrame(frame);
if (face != null)
{
// Лимит частоты отправок (не чаще раза в 2 секунды)
if ((DateTime.Now - _lastTime).TotalMilliseconds > 2000)
{
_lastTime = DateTime.Now;
_onDetected(face);
string status = face.IsRecognized
? $"✅ Recognized: {face.MatchedEmployee?.Name}"
: "❌ Unknown face";
Console.WriteLine($"{DateTime.Now:HH:mm:ss} - {status}");
}
}
}
}
else
{
// Если Read вернул false, возможно потеря связи
Console.WriteLine("⚠️ Failed to read frame, retrying...");
await Task.Delay(500, token);
}
// Небольшая задержка, чтобы не грузить процессор на 100%
await Task.Delay(50, token);
}
catch (OperationCanceledException)
{
// Это нормальная ситуация при остановке
break;
}
catch (Exception ex)
{
Console.WriteLine($"⚠️ Error in camera loop: {ex.Message}");
await Task.Delay(1000, token);
}
}
Console.WriteLine("🛑 Camera loop finished.");
}
public void Stop()
{
if (_cts == null) return;
Console.WriteLine("Stopping camera service...");
_cts.Cancel();
try
{
// Ждем завершения задачи, но не бесконечно
if (_task != null)
{
_task.Wait(TimeSpan.FromSeconds(5));
}
}
catch (AggregateException ex)
{
// Игнорируем ошибки отмены задачи
foreach (var inner in ex.InnerExceptions)
{
if (!(inner is TaskCanceledException || inner is OperationCanceledException))
{
Console.WriteLine($"Unexpected error during stop: {inner.Message}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error stopping task: {ex.Message}");
}
}
public void Dispose()
{
if (_isDisposed) return;
Stop();
_capture?.Dispose();
_cts?.Dispose();
_isDisposed = true;
Console.WriteLine("Camera resources released.");
}
}
@@ -0,0 +1,65 @@
using Microsoft.Data.Sqlite;
using FaceRecognitionApp.Models;
namespace FaceRecognitionApp.Services;
public class DatabaseService
{
private readonly string _connectionString = "Data Source=faces.db";
public DatabaseService()
{
InitializeDatabase();
}
private void InitializeDatabase()
{
using var connection = new SqliteConnection(_connectionString);
connection.Open();
var cmd = connection.CreateCommand();
cmd.CommandText = @"
CREATE TABLE IF NOT EXISTS Employees (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL,
Department TEXT,
FaceDescriptor BLOB,
Photo BLOB
)";
cmd.ExecuteNonQuery();
}
public void AddEmployee(string name, string? dept, byte[] descriptor, byte[] photo)
{
using var connection = new SqliteConnection(_connectionString);
connection.Open();
var cmd = connection.CreateCommand();
cmd.CommandText = "INSERT INTO Employees (Name, Department, FaceDescriptor, Photo) VALUES (@n, @d, @desc, @p)";
cmd.Parameters.AddWithValue("@n", name);
cmd.Parameters.AddWithValue("@d", (object?)dept ?? DBNull.Value);
cmd.Parameters.AddWithValue("@desc", descriptor);
cmd.Parameters.AddWithValue("@p", photo);
cmd.ExecuteNonQuery();
}
public List<Employee> GetAllEmployees()
{
var list = new List<Employee>();
using var connection = new SqliteConnection(_connectionString);
connection.Open();
var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT * FROM Employees";
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
list.Add(new Employee
{
Id = reader.GetInt32(0),
Name = reader.GetString(1),
Department = reader.IsDBNull(2) ? null : reader.GetString(2),
FaceDescriptor = (byte[])reader[3],
Photo = reader.IsDBNull(4) ? null : (byte[])reader[4]
});
}
return list;
}
}
@@ -0,0 +1,234 @@
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Face;
using Emgu.CV.Structure;
using Emgu.CV.Util;
using FaceRecognitionApp.Models;
using System.Drawing;
using System.Runtime.InteropServices;
namespace FaceRecognitionApp.Services;
public class FaceRecognitionService : IDisposable
{
private readonly LBPHFaceRecognizer _recognizer;
private readonly CascadeClassifier _cascade;
private readonly DatabaseService _db;
private List<Employee> _employees = new();
private bool _isTrained = false;
// Коэффициент уменьшения кадра для ускорения поиска
private const double ResizeScale = 0.5;
private const int OriginalMinSize = 30;
private readonly Size _scaledMinSize;
public FaceRecognitionService(DatabaseService db)
{
_db = db;
var cascadePath = Path.Combine(AppContext.BaseDirectory, "haarcascade_frontalface_default.xml");
if (!File.Exists(cascadePath))
throw new FileNotFoundException($"Cascade file missing at: {cascadePath}");
_cascade = new CascadeClassifier(cascadePath);
_recognizer = new LBPHFaceRecognizer(1, 8, 8, 8, 80.0);
// Вычисляем минимальный размер лица для уменьшенного кадра
int scaledMin = (int)(OriginalMinSize * ResizeScale);
_scaledMinSize = new Size(Math.Max(scaledMin, 10), Math.Max(scaledMin, 10));
TrainModel();
}
private void TrainModel()
{
_employees = _db.GetAllEmployees();
if (!_employees.Any()) return;
using var images = new VectorOfMat();
using var labels = new VectorOfInt();
foreach (var emp in _employees)
{
if (emp.Photo == null || emp.Photo.Length == 0) continue;
try
{
using var ms = new MemoryStream(emp.Photo);
using var bmp = new Bitmap(ms);
using var mat = BitmapToMat(bmp);
using var gray = new Mat();
CvInvoke.CvtColor(mat, gray, ColorConversion.Bgr2Gray);
// Ищем лицо на фото сотрудника для обрезки
var rects = _cascade.DetectMultiScale(gray, 1.1, 5, new Size(30, 30));
if (rects.Length > 0)
{
using var faceMat = new Mat(gray, rects[0]);
using var resized = new Mat();
CvInvoke.Resize(faceMat, resized, new Size(100, 100));
images.Push(resized);
labels.Push(new int[] { emp.Id });
}
}
catch (Exception ex)
{
Console.WriteLine($"Training error for {emp.Name}: {ex.Message}");
}
}
if (images.Size > 0)
{
_recognizer.Train(images, labels);
_isTrained = true;
Console.WriteLine($"✅ Trained on {images.Size} faces.");
}
else
{
Console.WriteLine("⚠️ No valid faces found for training.");
}
}
public DetectedFace? ProcessFrame(Mat frame)
{
if (frame == null || frame.IsEmpty) return null;
// === УСКОРЕНИЕ: Уменьшаем кадр для поиска лиц ===
using var smallFrame = new Mat();
CvInvoke.Resize(frame, smallFrame, new Size(), ResizeScale, ResizeScale, Inter.Linear);
using var gray = new Mat();
CvInvoke.CvtColor(smallFrame, gray, ColorConversion.Bgr2Gray);
// Ищем лица на уменьшенном кадре
var rects = _cascade.DetectMultiScale(gray, 1.1, 5, _scaledMinSize);
if (rects.Length == 0)
return null;
var smallRect = rects[0];
// === ВОССТАНОВЛЕНИЕ координат к оригинальному размеру ===
int invScale = (int)(1.0 / ResizeScale);
var originalRect = new Rectangle(
smallRect.X * invScale,
smallRect.Y * invScale,
smallRect.Width * invScale,
smallRect.Height * invScale
);
// Проверка границ по оригинальному кадру
if (originalRect.X < 0 || originalRect.Y < 0 ||
originalRect.Right > frame.Width || originalRect.Bottom > frame.Height)
return null;
// Вырезаем лицо из ОРИГИНАЛЬНОГО кадра (лучшее качество для распознавания)
using var faceMat = new Mat(frame, originalRect);
using var faceGray = new Mat();
CvInvoke.CvtColor(faceMat, faceGray, ColorConversion.Bgr2Gray);
using var resized = new Mat();
CvInvoke.Resize(faceGray, resized, new Size(100, 100));
var result = new DetectedFace();
if (_isTrained)
{
try
{
var prediction = _recognizer.Predict(resized);
int label = prediction.Label;
double confidence = prediction.Distance;
if (label != -1 && confidence < 80)
{
result.IsRecognized = true;
result.MatchedEmployee = _employees.FirstOrDefault(e => e.Id == label);
result.Confidence = confidence;
}
}
catch (Exception ex)
{
Console.WriteLine($"Prediction error: {ex.Message}");
}
}
// Конвертация в JPEG для веб-интерфейса
using var ms = new MemoryStream();
using var bmp = MatToBitmap(resized);
bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
result.ImageData = ms.ToArray();
return result;
}
/// <summary>
/// Безопасная конвертация Bitmap → Mat через промежуточный буфер
/// </summary>
private static Mat BitmapToMat(Bitmap bitmap)
{
var mat = new Mat(bitmap.Height, bitmap.Width, DepthType.Cv8U, 3);
var data = bitmap.LockBits(
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
try
{
int length = Math.Abs(data.Stride) * bitmap.Height;
byte[] buffer = new byte[length];
Marshal.Copy(data.Scan0, buffer, 0, length);
Marshal.Copy(buffer, 0, mat.DataPointer, length);
}
finally
{
bitmap.UnlockBits(data);
}
return mat;
}
/// <summary>
/// Безопасная конвертация Mat → Bitmap через промежуточный буфер
/// </summary>
private static Bitmap MatToBitmap(Mat mat)
{
var colorMat = new Mat();
if (mat.NumberOfChannels == 1)
CvInvoke.CvtColor(mat, colorMat, ColorConversion.Gray2Bgr);
else
mat.CopyTo(colorMat);
var bmp = new Bitmap(colorMat.Width, colorMat.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
var data = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
System.Drawing.Imaging.ImageLockMode.WriteOnly,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
try
{
int length = Math.Abs(data.Stride) * colorMat.Height;
byte[] buffer = new byte[length];
Marshal.Copy(colorMat.DataPointer, buffer, 0, length);
Marshal.Copy(buffer, 0, data.Scan0, length);
}
finally
{
bmp.UnlockBits(data);
colorMat.Dispose();
}
return bmp;
}
public void Dispose()
{
_cascade?.Dispose();
_recognizer?.Dispose();
}
}
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Camera": {
"Url": "rtsp://admin:4NUDZhJ7@172.22.10.101:554/cam/realmonitor?channel=1&subtype=0"
}
}
Binary file not shown.
File diff suppressed because it is too large Load Diff
+73
View File
@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html>
<head>
<title>Face Recognition</title>
<style>
body {
font-family: sans-serif;
background: #222;
color: #fff;
text-align: center;
}
#gallery {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin-top: 20px;
}
.card {
background: #333;
padding: 10px;
border-radius: 8px;
width: 200px;
}
img {
width: 100%;
border: 3px solid red;
border-radius: 4px;
}
.known img {
border-color: green;
}
.name {
margin-top: 5px;
font-weight: bold;
}
.time {
font-size: 0.8em;
color: #aaa;
}
</style>
</head>
<body>
<h1>Live Face Detection</h1>
<div id="gallery"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js"></script>
<script>
const connection = new signalR.HubConnectionBuilder().withUrl("/faceHub").build();
connection.on("NewFace", (data) => {
const div = document.createElement('div');
div.className = data.isRecognized ? 'card known' : 'card';
div.innerHTML = `
<img src="data:image/jpeg;base64,${data.image}" />
<div class="name">${data.isRecognized ? data.name : 'Unknown'}</div>
<div class="time">${new Date().toLocaleTimeString()}</div>
`;
const gallery = document.getElementById('gallery');
gallery.insertBefore(div, gallery.firstChild);
if (gallery.children.length > 20) gallery.lastChild.remove();
});
connection.start().catch(console.error);
</script>
</body>
</html>
+2 -3
View File
@@ -5,8 +5,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" /> <base href="/" />
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css" /> <link rel="stylesheet" href="~/bootstrap/css/bootstrap.css" />
<link rel="stylesheet" href="app.css" /> <link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="Ministreliy.styles.css" /> <link rel="stylesheet" href="Ministreliy.styles.css" />
<link rel="icon" type="image/png" href="favicon.png" /> <link rel="icon" type="image/png" href="favicon.png" />
@@ -15,8 +14,8 @@
<body> <body>
<Routes @rendermode="InteractiveServer" /> <Routes @rendermode="InteractiveServer" />
<script src="~/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="_framework/blazor.web.js"></script> <script src="_framework/blazor.web.js"></script>
<script src="/bootstrap/js/bootstrap.bundle.js"></script>
</body> </body>
</html> </html>
+60 -11
View File
@@ -1,23 +1,72 @@
@page "/database" @page "/database"
@using Ministreliy.Models
@using Newtonsoft.Json
@using System.Text
@inject IJSRuntime JS
@inject NavigationManager NavManager
<PageTitle>База данных</PageTitle> <PageTitle>База данных</PageTitle>
<ul class="nav nav-tabs"> <div>
<li class="nav-item"> <div class="row justify-content-between">
<a class="nav-link active" aria-current="page" href="database">Сводные данные</a> <div class="col-10">
</li> <ul class="nav nav-tabs">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="database/type">Типы устройств</a> <a class="nav-link active" aria-current="page" href="database">Сводные данные</a>
</li> </li>
<li class="nav-item">
<a class="nav-link" href="database/type">Типы устройств</a>
</li>
<li class="nav-item">
<a class="nav-link" href="database/device">Устройства</a>
</li>
</ul> </ul>
</div>
<div class="col-2">
@* <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addtype">
Добавить тип
</button>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#deltype">
Удалить тип
</button> *@
</div>
</div>
<div class="row">
</div>
</div>
@if (listDeviceTypes.Count == 0 || listDeviceItems.Count == 0)
{
<p>@messageStatus</p>
}
@code { @code {
private int currentCount = 0; public static List<DeviceType>? listDeviceTypes = new();
public static List<DeviceItem>? listDeviceItems = new();
public static HttpClient httpClient = new();
private void IncrementCount() string? messageStatus = "Получаю данные...";
protected override async Task OnInitializedAsync()
{ {
currentCount++; httpClient.BaseAddress = new Uri("http://localhost:5262/api/");
var devicetype = await httpClient.GetAsync("Device/GetTypes");
var deviceitem = await httpClient.GetAsync("Device/GetDevices");
listDeviceTypes = JsonConvert.DeserializeObject<List<DeviceType>>(await devicetype.Content.ReadAsStringAsync());
listDeviceItems = JsonConvert.DeserializeObject<List<DeviceItem>>(await deviceitem.Content.ReadAsStringAsync());
messageStatus = string.Empty;
if (listDeviceTypes.Count == 0)
{
messageStatus += $"нет типов устройств\r\n";
}
if (listDeviceItems.Count == 0)
{
messageStatus += $"нет устройств\r\n";
}
DataStore.ListDeviceTypesStore = listDeviceTypes;
DataStore.ListDeviceItemsStore = listDeviceItems;
} }
} }
@@ -0,0 +1,201 @@
@page "/database/device"
@using Ministreliy.Models
@using System.Text.Json
@using Newtonsoft.Json
@using System.Text
@inject IJSRuntime JS
@inject NavigationManager NavManager
<PageTitle>База данных</PageTitle>
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" aria-current="page" href="database">Сводные данные</a>
</li>
<li class="nav-item">
<a class="nav-link" href="database/type">Типы устройств</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="database/device">Устройства</a>
</li>
</ul>
<div class="modal fade modal1" id="addtype" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="addtypeLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="addtypeLabel">Заголовок</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="typeID" class="form-label">id типа</label>
<input type="number" class="form-control" id="typeID" @bind="id_type" />
</div>
<div class="mb-3">
<label for="typeName" class="form-label">Имя типа</label>
<textarea class="form-control" id="typeName" rows="3" @bind="name_type"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" @onclick="SaveType">Сохранить</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
</div>
</div>
</div>
</div>
@if (listDT.Count == 0 || listDT == null)
{
<p>@msg</p>
}
else
{
@* <ul class="nav nav-tabs">
<li class="nav-item" role="presentation">
<a class="nav-link" aria-current="page" href="database">Сводные данные</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link active"
id="addtype-tab"
data-bs-toggle="tab"
data-bs-target="#addtype"
role="tab"
aria-controls="addtype"
aria-selected="true"
href="database/addtype">Добавить Тип</a>
</li>
</ul> *@
<br>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addtype">
Добавить тип
</button>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#deltype">
Удалить тип
</button>
<div class="card-body p-0">
<div class="tab-content" id="myTabContent">
<!-- Панель 1: Главная -->
<div class="tab-pane fade show active" id="addtype" role="tabpanel" aria-labelledby="addtype-tab">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Название</th>
</tr>
</thead>
<tbody>
@foreach (var item in listDT)
{
<tr>
<th scope="row">@item.Id</th>
<td>@item.Name</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
<div class="modal fade modal2" id="deltype" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="deltypeLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="deltypeLabel">Заголовок</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<select class="form-select" @bind="selected_type" aria-label="Вибрать тип">
@foreach (var item in listDT)
{
<option value="@item.Id">@item.Name</option>
}
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" @onclick="DelType">Удалить</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
</div>
</div>
</div>
</div>
}
@code {
//HttpClient http = new();
private DeviceType dt;
private List<DeviceType> listDT = new();
private string msg = "-= ПОЛУЧАЮ ДАННЫЕ =-", name_type = "";
private int id_type = 1, selected_type = 0;
protected override async Task OnInitializedAsync()
{
HttpClient http = new();
await Task.Delay(1000);
http.BaseAddress = new Uri("http://localhost:5262/api/");
var t = await http.GetAsync("Device/GetTypes");
if (t.StatusCode == System.Net.HttpStatusCode.NoContent)
{
msg = "-= ДАННЫХ НЕТ =-";
}
else
{
listDT = JsonConvert.DeserializeObject<List<DeviceType>>(await t.Content.ReadAsStringAsync());
//id_type = listDT.Count + 1;
var a = listDT.Select(listDT => listDT.Id).ToList();
id_type = Enumerable.Range(1, int.MaxValue).FirstOrDefault(i => !a.Contains(i));
}
}
private async Task SaveType()
{
HttpClient http = new();
dt = new();
dt.Id = id_type;
dt.Name = name_type;
http.BaseAddress = new Uri("http://localhost:5262/api/");
using StringContent content = new StringContent(
JsonConvert.SerializeObject(dt),
Encoding.UTF8,
"application/json"
);
var r = await http.PostAsync("Device/AddType", content);
if (r.IsSuccessStatusCode)
{
await JS.InvokeVoidAsync("eval", $"var myModal = bootstrap.Modal.getInstance(document.querySelector('.modal1')); myModal.hide();");
await Task.Delay(150);
id_type = listDT.Count+1;
NavManager.NavigateTo(NavManager.Uri, forceLoad: true);
}
}
private async Task DelType()
{
HttpClient http = new();
http.BaseAddress = new Uri("http://localhost:5262/api/");
var r = await http.DeleteAsync($"Device/DelType?id={selected_type}");
if (r.IsSuccessStatusCode)
{
await JS.InvokeVoidAsync("eval", $"var myModal = bootstrap.Modal.getInstance(document.querySelector('.modal2')); myModal.hide();");
await Task.Delay(250);
id_type = listDT.Count + 1;
NavManager.NavigateTo(NavManager.Uri, forceLoad: true);
}
}
}
+99 -96
View File
@@ -3,19 +3,69 @@
@using System.Text.Json @using System.Text.Json
@using Newtonsoft.Json @using Newtonsoft.Json
@using System.Text @using System.Text
@inject IJSRuntime JS @inject IJSRuntime JS
@inject NavigationManager NavManager @inject NavigationManager NavManager
<PageTitle>База данных</PageTitle> <PageTitle>База данных</PageTitle>
<div class="row justify-content-between">
<div class="col-10">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" aria-current="page" href="database">Сводные данные</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="database/type">Типы устройств</a>
</li>
<li class="nav-item">
<a class="nav-link" href="database/device">Устройства</a>
</li>
</ul>
</div>
<div class="col-2">
<button type="button" class="btn btn-primary" @onclick="OpenModal">
Добавить тип
</button>
<button type="button" class="btn btn-primary" @onclick="DelModal">
Удалить тип
</button>
</div>
</div>
@if (listDT.Count == 0)
{
<p>@msg</p>
}
else
{
<div class="card-body p-0">
<div class="tab-content" id="myTabContent">
<!-- Панель 1: Главная -->
<div class="tab-pane fade show active" id="addtype" role="tabpanel" aria-labelledby="addtype-tab">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Название</th>
</tr>
</thead>
<tbody>
@foreach (var item in Database.listDeviceTypes)
{
<tr>
<th scope="row">@item.Id</th>
<td>@item.Name</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
<div class="modal fade modal1" id="addtype" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="addtypeLabel" aria-hidden="true">
}
<div class="modal fade modal1" id="addtype" tabindex="-1" aria-labelledby="addtypeLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="addtypeLabel">Заголовок</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body"> <div class="modal-body">
<div class="mb-3"> <div class="mb-3">
<label for="typeID" class="form-label">id типа</label> <label for="typeID" class="form-label">id типа</label>
@@ -33,117 +83,67 @@
</div> </div>
</div> </div>
</div> </div>
<div class="modal fade modal2" id="deltype" tabindex="-1" aria-labelledby="deltypeLabel" aria-hidden="true">
<div class="modal-dialog">
@if (listDT.Count == 0 || listDT == null) <div class="modal-content">
{ <div class="modal-header">
<p>@msg</p> <h1 class="modal-title fs-5" id="deltypeLabel">Заголовок</h1>
} <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
else </div>
{ <div class="modal-body">
<ul class="nav nav-tabs"> <div class="mb-3">
<li class="nav-item" role="presentation"> <select class="form-select" @bind="selected_type" aria-label="Вибрать тип">
<a class="nav-link" aria-current="page" href="database">Сводные данные</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link active"
id="addtype-tab"
data-bs-toggle="tab"
data-bs-target="#addtype"
role="tab"
aria-controls="addtype"
aria-selected="true"
href="database/addtype">Добавить Тип</a>
</li>
</ul>
<div class="card-body p-0">
<div class="tab-content" id="myTabContent">
<!-- Панель 1: Главная -->
<div class="tab-pane fade show active" id="addtype" role="tabpanel" aria-labelledby="addtype-tab">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Название</th>
</tr>
</thead>
<tbody>
@foreach (var item in listDT) @foreach (var item in listDT)
{ {
<tr> <option value="@item.Id">@item.Name</option>
<th scope="row">@item.Id</th>
<td>@item.Name</td>
</tr>
} }
</tbody> </select>
</table> </div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" @onclick="DelType">Удалить</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
</div> </div>
</div> </div>
</div> </div>
</div>
<script>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#deltype"> // Функции для работы с модальными окнами
Удалить тип window.openModal = function (modalId) {
</button> const modalElement = document.getElementById(modalId);
if (modalElement) {
const modal = new bootstrap.Modal(modalElement);
modal.show();
<div class="modal fade modal2" id="deltype" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="deltypeLabel" aria-hidden="true"> } else {
<div class="modal-dialog"> console.error('Модальное окно с id "' + modalId + '" не найдено');
<div class="modal-content"> }
<div class="modal-header"> };
<h1 class="modal-title fs-5" id="deltypeLabel">Заголовок</h1> </script>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<select class="form-select" @bind="selected_type" aria-label="Вибрать тип">
@foreach (var item in listDT)
{
<option value="@item.Id">@item.Name</option>
}
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" @onclick="DelType">Удалить</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
</div>
</div>
</div>
</div>
}
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addtype">
Добавить тип
</button>
@code { @code {
//HttpClient http = new(); //HttpClient http = new();
private DeviceType dt; private DeviceType dt;
private List<DeviceType> listDT = new(); private List<DeviceType> listDT;
private string msg = "-= ПОЛУЧАЮ ДАННЫЕ =-", name_type = ""; private string msg = "-= ПОЛУЧАЮ ДАННЫЕ =-", name_type = "";
private int id_type = 1, selected_type = 0; private int id_type = 1, selected_type = 0;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
HttpClient http = new(); listDT = Database.listDeviceTypes;
await Task.Delay(1000); if (listDT.Count == 0)
http.BaseAddress = new Uri("http://localhost:5262/api/");
var t = await http.GetAsync("Device/GetTypes");
if (t.StatusCode == System.Net.HttpStatusCode.NoContent)
{ {
msg = "-= ДАННЫХ НЕТ =-"; msg = "-= ДАННЫХ НЕТ =-";
} }
else else
{ {
listDT = JsonConvert.DeserializeObject<List<DeviceType>>(await t.Content.ReadAsStringAsync());
//id_type = listDT.Count + 1; //id_type = listDT.Count + 1;
var a = listDT.Select(listDT => listDT.Id).ToList(); var a = listDT.Select(listDT => listDT.Id).ToList();
id_type = Enumerable.Range(1, int.MaxValue).FirstOrDefault(i => !a.Contains(i)); //id_type = Enumerable.Range(1, int.MaxValue).FirstOrDefault(i => !a.Contains(i));
} }
} }
private async Task SaveType() private async Task SaveType()
@@ -154,16 +154,16 @@ else
dt.Name = name_type; dt.Name = name_type;
http.BaseAddress = new Uri("http://localhost:5262/api/"); http.BaseAddress = new Uri("http://localhost:5262/api/");
using StringContent content = new StringContent( using StringContent content = new StringContent(
JsonConvert.SerializeObject(dt), JsonConvert.SerializeObject(dt),
Encoding.UTF8, Encoding.UTF8,
"application/json" "application/json"
); );
var r = await http.PostAsync("Device/AddType", content); var r = await http.PostAsync("Device/AddType", content);
if (r.IsSuccessStatusCode) if (r.IsSuccessStatusCode)
{ {
await JS.InvokeVoidAsync("eval", $"var myModal = bootstrap.Modal.getInstance(document.querySelector('.modal1')); myModal.hide();"); await JS.InvokeVoidAsync("eval", $"var myModal = bootstrap.Modal.getInstance(document.querySelector('.modal1')); myModal.hide();");
await Task.Delay(150); await Task.Delay(150);
id_type = listDT.Count+1; id_type = listDT.Count + 1;
NavManager.NavigateTo(NavManager.Uri, forceLoad: true); NavManager.NavigateTo(NavManager.Uri, forceLoad: true);
} }
} }
@@ -180,6 +180,9 @@ else
NavManager.NavigateTo(NavManager.Uri, forceLoad: true); NavManager.NavigateTo(NavManager.Uri, forceLoad: true);
} }
} }
private async Task OpenModal() => await JS.InvokeVoidAsync("openModal", "addtype");
private async Task DelModal() => await JS.InvokeVoidAsync("openModal", "deltype");
} }
@@ -1,63 +0,0 @@
@page "/weather"
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate a loading indicator
await Task.Delay(500);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
}
private class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}
+2
View File
@@ -7,6 +7,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Blazorise.Bootstrap5" Version="2.2.1" />
<PackageReference Include="Blazorise.Icons.FontAwesome" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup> </ItemGroup>
+8
View File
@@ -0,0 +1,8 @@
namespace Ministreliy.Models
{
public class DataStore
{
public static List<DeviceType>? ListDeviceTypesStore;
public static List<DeviceItem>? ListDeviceItemsStore;
}
}
+25
View File
@@ -0,0 +1,25 @@
namespace Ministreliy.Models
{
public class DeviceItem
{
public int Id { get; set; }
public string Ip { get; set; }
public string Mac { get; set; }
public string? Description { get; set; }
public string? Name { get; set; }
public string? Location { get; set; }
public List<DeviceItem> Devises { get; set; }
public DeviceType DeviceType { get; set; }
public int ConnectedToSwitchId { get; set; }
public int ConnectedToPortOnSwitch { get; set; }
}
//Unknown = 0,
//SwitchCore = 1,
//SwitchAccess = 2,
//Camera = 3,
//Server = 4,
//WorkStation = 5,
//PDU_AVR = 6,
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+601
View File
@@ -0,0 +1,601 @@
/*!
* Bootstrap Reboot v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
line-height: inherit;
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type=search]::-webkit-search-cancel-button {
cursor: pointer;
filter: grayscale(1);
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+598
View File
@@ -0,0 +1,598 @@
/*!
* Bootstrap Reboot v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
}
a:hover {
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
line-height: inherit;
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type=search]::-webkit-search-cancel-button {
cursor: pointer;
filter: grayscale(1);
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+34 -34
View File
@@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Strela.Models; using Strela.Models;
using Strela.Services; using Strela.Services;
@@ -10,31 +9,21 @@ namespace Strela.Controllers;
[Route("api/[controller]")] [Route("api/[controller]")]
public class DeviceController : ControllerBase public class DeviceController : ControllerBase
{ {
private readonly ILogger<UserController> _logger; private readonly ILogger<DeviceController> _logger;
public DeviceController(ILogger<UserController> logger) public DeviceController(ILogger<DeviceController> logger)
{ {
_logger = logger; _logger = logger;
} }
//=========================== GET =================================//
[HttpGet("GetTypes")]
[ProducesResponseType<List<DeviceType>>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public IActionResult GetList()
{
using (var db = new SQLiteService())
{
var tps = db.DeviceTypes.ToList();
if (tps.Count != 0)
return Ok(tps);
else return NoContent();
}
}
[HttpGet("GetDevice")] //=========================== GET =================================//
#region GET
[HttpGet("GetDeviceById")]
[ProducesResponseType<DeviceItem>(StatusCodes.Status200OK)] [ProducesResponseType<DeviceItem>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public IActionResult GetById(int id) public IActionResult GetById(int id) // Получить устройство по id
{ {
using (var db = new SQLiteService()) using (var db = new SQLiteService())
{ {
@@ -45,9 +34,25 @@ public class DeviceController : ControllerBase
} }
} }
[HttpGet("GetDevices")]
[ProducesResponseType<DeviceItem>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public IActionResult GetAll() // Получить все устройства
{
using (var db = new SQLiteService())
{
var d = db.Devices.ToList();
//if (d.Count != 0)
return Ok(d);
//else return NoContent();
}
}
#endregion
//========================= POST ====================================// //========================= POST ====================================//
#region POST
[HttpPost("AddDevice")] [HttpPost("AddDevice")]
public IActionResult AddDevice(DeviceItem d) public IActionResult AddDevice(DeviceItem d) // Добавить одно устройство
{ {
using (var db = new SQLiteService()) using (var db = new SQLiteService())
{ {
@@ -57,27 +62,22 @@ public class DeviceController : ControllerBase
} }
} }
[HttpPost("AddType")]
public IActionResult AddType(DeviceType d) #endregion
{
using (var db = new SQLiteService())
{
db.DeviceTypes.Add(d);//.Where(u => u.Name == name).FirstOrDefault();
db.SaveChanges();
return Created();
}
}
//========================= DELETE ====================================// //========================= DELETE ====================================//
[HttpDelete("DelType")] #region DELETE
public IActionResult DelType(int id)
[HttpDelete("DelDevice")]
public IActionResult DelDevice(int id) //Удалить устройство по id
{ {
using (var db = new SQLiteService()) using (var db = new SQLiteService())
{ {
var deldev = db.DeviceTypes.Where(u => u.Id == id).FirstOrDefault(); var deldev = db.Devices.Where(u => u.Id == id).FirstOrDefault();
db.DeviceTypes.Remove(deldev); db.Devices.Remove(deldev);
db.SaveChanges(); db.SaveChanges();
return Ok(); return Ok();
} }
} }
#endregion
} }
+82
View File
@@ -0,0 +1,82 @@
using Microsoft.AspNetCore.Mvc;
using Strela.Models;
using Strela.Services;
namespace Strela.Controllers;
[ApiController]
[Route("api/[controller]")]
public class ObjectController : ControllerBase
{
private readonly ILogger<ObjectController> _logger;
public ObjectController(ILogger<ObjectController> logger)
{
_logger = logger;
}
//=========================== GET =================================//
[HttpGet("GetObject")]
public IActionResult GetList()
{
using (var db = new SQLiteService())
{
var oi = db.ObjectItems.ToList();
return Ok(oi);
}
}
[HttpGet("GetObjectById")]
[ProducesResponseType<ObjectItem>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public IActionResult GetById(int id)
{
using (var db = new SQLiteService())
{
var o = db.ObjectItems.Where(o => o.Id == id).FirstOrDefault();
if (o != null)
return Ok(o);
else return NoContent();
}
}
[HttpGet("GetObjectByName")]
[ProducesResponseType<ObjectItem>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public IActionResult GetByName(string name)
{
using (var db = new SQLiteService())
{
var o = db.ObjectItems.Where(ob => ob.NameObject == name).FirstOrDefault();
if (o != null)
return Ok(o);
else return NoContent();
}
}
//========================= POST ====================================//
[HttpPost("AddObject")]
public IActionResult AddObject(ObjectItem o)
{
using (var db = new SQLiteService())
{
db.ObjectItems.Add(o);
db.SaveChanges();
return Created();
}
}
//========================= DELETE ====================================//
[HttpDelete("DelObject")]
public IActionResult DelObject(int id)
{
using (var db = new SQLiteService())
{
var delobj = db.ObjectItems.Where(o => o.Id == id).FirstOrDefault();
db.ObjectItems.Remove(delobj);
db.SaveChanges();
return Ok();
}
}
}
+77
View File
@@ -0,0 +1,77 @@
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Strela.Models;
using Strela.Services;
namespace Strela.Controllers;
[ApiController]
[Route("api/[controller]")]
public class TypeController : ControllerBase
{
private readonly ILogger<TypeController> _logger;
public TypeController(ILogger<TypeController> logger)
{
_logger = logger;
}
//=========================== GET =================================//
#region GET
[HttpGet("GetTypes")]
public IActionResult GetList()// Получить лист типов устройств
{
using (var db = new SQLiteService())
{
var tps = db.DeviceTypes.ToList();
return Ok(tps);
}
}
[HttpGet("GetTypeById")]
[ProducesResponseType<DeviceItem>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public IActionResult GetById(int id) // Получить устройство по id
{
using (var db = new SQLiteService())
{
var d = db.DeviceTypes.Where(d => d.Id == id).FirstOrDefault();
if (d != null)
return Ok(d);
else return NoContent();
}
}
#endregion
//========================= POST ====================================//
#region POST
[HttpPost("AddType")]
public IActionResult AddType(DeviceType d) //Добавить один тип устройства
{
using (var db = new SQLiteService())
{
db.DeviceTypes.Add(d);//.Where(u => u.Name == name).FirstOrDefault();
db.SaveChanges();
return Created();
}
}
#endregion
//========================= DELETE ====================================//
#region DELETE
[HttpDelete("DelType")]
public IActionResult DelType(int id) //Удалить тип устройства по id
{
using (var db = new SQLiteService())
{
var deldevtype = db.DeviceTypes.Where(u => u.Id == id).FirstOrDefault();
db.DeviceTypes.Remove(deldevtype);
db.SaveChanges();
return Ok();
}
}
#endregion
}
+14 -17
View File
@@ -2,29 +2,26 @@
{ {
public class DeviceItem public class DeviceItem
{ {
public int Id { get; set; } public Guid Id { get; set; }
public string Ip { get; set; } public string? Ip { get; set; }
public string Mac { get; set; } public string? Mac { get; set; }
public string? Description { get; set; } public string? Description { get; set; }
public string? Name { get; set; } public string? Name { get; set; }
public string? Location { get; set; } public string? Location { get; set; }
public List<DeviceItem> Devises { get; set; } public List<DeviceItem>? ListDevices { get; set; }
public DeviceType DeviceType { get; set; } public DeviceType? DeviceType { get; set; }
public int ConnectedToSwitchId { get; set; } public ObjectItem? DeviceObject { get; set; }
public Guid ConnectedToSwitchId { get; set; }
public int ConnectedToPortOnSwitch { get; set; } public int ConnectedToPortOnSwitch { get; set; }
//public DeviceItem(int type, string ip, string mac, string location)
//{
//}
} }
//Unknown = 0, //Unknown = 0,
//SwitchCore = 1, //SwitchCore = 1,
//SwitchAccess = 2, //SwitchAccess = 2,
//Camera = 3, //Camera = 3,
//Server = 4, //Server = 4,
//WorkStation = 5, //WorkStation = 5,
//PDU_AVR = 6, //PDU_AVR = 6,
} }
+2 -1
View File
@@ -2,7 +2,8 @@
{ {
public class DeviceType public class DeviceType
{ {
public int Id { get; set; } public Guid Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string? Description { get; set; }
} }
} }
+9
View File
@@ -0,0 +1,9 @@
namespace Strela.Models
{
public class ObjectItem
{
public Guid Id { get; set; }
public string NameObject { get; set; } = "Unknown";
public string? DescriptionObject { get; set; } = null;
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -9,6 +9,7 @@ namespace Strela.Services
public virtual DbSet<User> Users { get; set; } public virtual DbSet<User> Users { get; set; }
public virtual DbSet<DeviceItem> Devices { get; set; } public virtual DbSet<DeviceItem> Devices { get; set; }
public virtual DbSet<DeviceType> DeviceTypes { get; set; } public virtual DbSet<DeviceType> DeviceTypes { get; set; }
public virtual DbSet<ObjectItem> ObjectItems { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+7 -4
View File
@@ -8,6 +8,13 @@
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="Models\OTD\**" />
<Content Remove="Models\OTD\**" />
<EmbeddedResource Remove="Models\OTD\**" />
<None Remove="Models\OTD\**" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.16" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.16" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.16" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.16" />
@@ -23,8 +30,4 @@
<ProjectReference Include="..\lsSoft.ServiceDefaults\lsSoft.ServiceDefaults.csproj" /> <ProjectReference Include="..\lsSoft.ServiceDefaults\lsSoft.ServiceDefaults.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Models\OTD\" />
</ItemGroup>
</Project> </Project>
+25
View File
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.32916.344
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Visor", "Visor\Visor.csproj", "{A1073A31-9C1C-4B5F-8752-F4397F84E210}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A1073A31-9C1C-4B5F-8752-F4397F84E210}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1073A31-9C1C-4B5F-8752-F4397F84E210}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1073A31-9C1C-4B5F-8752-F4397F84E210}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1073A31-9C1C-4B5F-8752-F4397F84E210}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C59BEA9D-058E-406A-BE53-A3BC184DCF72}
EndGlobalSection
EndGlobal
+8
View File
@@ -0,0 +1,8 @@
{
"servers": {
"blazorise-docs": {
"type": "http",
"url": "https://mcp.blazorise.com/mcp"
}
}
}
+47
View File
@@ -0,0 +1,47 @@
<Blazorise.ThemeProvider Theme="@theme">
<Router AppAssembly="typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Visor.Layouts.MainLayout)" />
</Found>
<NotFound>
<p>Sorry, there's nothing at this address.</p>
</NotFound>
</Router>
<MessageProvider />
<PageProgressProvider />
</Blazorise.ThemeProvider>
@code {
private Theme theme = new()
{
BarOptions = new()
{
HorizontalHeight = "72px"
},
ColorOptions = new()
{
Primary = "#0288D1",
Secondary = "#A65529",
Success = "#23C02E",
Info = "#9BD8FE",
Warning = "#F8B86C",
Danger = "#F95741",
Light = "#F0F0F0",
Dark = "#535353",
},
BackgroundOptions = new()
{
Primary = "#0288D1",
Secondary = "#A65529",
Success = "#23C02E",
Info = "#9BD8FE",
Warning = "#F8B86C",
Danger = "#F95741",
Light = "#F0F0F0",
Dark = "#535353",
},
InputOptions = new()
{
CheckColor = "#0288D1",
}
};
}
@@ -0,0 +1,4 @@
@using System.Reflection
<Bar Padding="Padding.Is1" Background="Background.Default">
<Heading TextAlignment="TextAlignment.Center" Width="Width.Is100" TextColor="TextColor.White" Size="HeadingSize.Is6">@($"{Assembly.GetExecutingAssembly().GetName().Name} v{Assembly.GetExecutingAssembly().GetName().Version}")</Heading>
</Bar>
@@ -0,0 +1,49 @@
<Bar Breakpoint="Breakpoint.Desktop" NavigationBreakpoint="Breakpoint.Tablet" ThemeContrast="ThemeContrast.Dark"
Mode="BarMode.VerticalInline" CollapseMode="BarCollapseMode.Small">
<BarToggler />
<BarBrand>
<BarItem>
<BarLink To="">
<BarIcon IconName="customIcon" />
Visor
</BarLink>
</BarItem>
</BarBrand>
<BarMenu>
<BarStart>
<BarItem>
<BarLink To="/">
<BarIcon IconName="IconName.Dashboard" />
Дашборд
</BarLink>
</BarItem>
<BarItem>
<BarDropdown @bind-Visible="pagesBarVisible">
<BarDropdownToggle>
<BarIcon IconName="IconName.Edit" />
Настройка
</BarDropdownToggle>
<BarDropdownMenu>
<BarDropdownItem To="/setting/add_objects">Добавить объект</BarDropdownItem>
</BarDropdownMenu>
<BarDropdownMenu>
<BarDropdownItem To="/simple-datagrid">Simple DataGrid</BarDropdownItem>
</BarDropdownMenu>
</BarDropdown>
</BarItem>
</BarStart>
<BarEnd>
<BarItem>
<BarLink To="/setting/wizard">
<BarIcon IconName="IconName.CaretSquareRight" />
Wizard
</BarLink>
</BarItem>
</BarEnd>
</BarMenu>
</Bar>
@code {
private bool pagesBarVisible = false;
RenderFragment customIcon = @<img src="/brand-logo.png" style="width:32px; height: 32px" />;
}
@@ -0,0 +1,26 @@
<div>
@foreach ( var color in ThemeColors.Items.Values )
{
<div @key="color.Key">
@foreach ( var shade in color.Shades.Values )
{
var temp = shade.Value;
<div @key="shade.Key" class="demo-theme-color-item" style="background: @temp" @onclick="@(()=>OnThemeColorSelect(temp))"></div>
}
</div>
}
</div>
@code{
[Parameter]
public string? Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
Task OnThemeColorSelect( string value )
{
Value = value;
return ValueChanged.InvokeAsync(value);
}
}
@@ -0,0 +1,16 @@
.demo-theme-color-item {
display: table-cell;
width: 36px;
height: 36px;
vertical-align: top;
text-align: center;
color: white;
cursor: pointer;
}
.demo-theme-color-item .material-icons {
display: flex;
height: 100%;
align-items: center;
justify-content: center;
}
+163
View File
@@ -0,0 +1,163 @@
@using Blazorise.Localization
<Bar @bind-Visible="@topbarVisible" Breakpoint="Breakpoint.Desktop" Background="Background.Primary" ThemeContrast="ThemeContrast.Light">
<BarBrand>
<BarItem>
<BarLink To="">
<BarIcon Margin="Margin.Is2.FromEnd" IconName="IconName.Dashboard" />
Visor
</BarLink>
</BarItem>
</BarBrand>
<BarToggler />
<BarMenu>
<BarStart>
<BarItem>
<BarLink To="/">Home</BarLink>
</BarItem>
<BarItem>
<BarLink To="https://blazorise.com/docs/">Documentation</BarLink>
</BarItem>
<BarItem>
<BarDropdown>
<BarDropdownToggle>More</BarDropdownToggle>
<BarDropdownMenu>
<BarDropdownItem To="https://blazorise.com/docs/start/">
Quick-Start Guide
</BarDropdownItem>
<BarDropdownDivider />
<BarDropdownItem To="https://blazorise.com/docs/usage/">
Usage
</BarDropdownItem>
</BarDropdownMenu>
</BarDropdown>
</BarItem>
<BarItem>
<BarDropdown>
<BarDropdownToggle>Layout</BarDropdownToggle>
<BarDropdownMenu>
<BarDropdownItem Clicked="@(()=>OnLayoutTypeChecked("fixed-header"))">
@if ( LayoutType == "fixed-header" )
{
<Icon Name="IconName.CheckCircle" TextColor="TextColor.Success" />
}
Fixed Header
</BarDropdownItem>
<BarDropdownItem Clicked="@(()=>OnLayoutTypeChecked("fixed-header-footer-only"))">
@if ( LayoutType == "fixed-header-footer-only" )
{
<Icon Name="IconName.CheckCircle" TextColor="TextColor.Success" />
}
Fixed Header and Footer only
</BarDropdownItem>
<BarDropdownItem Clicked="@(()=>OnLayoutTypeChecked("sider-with-header-on-top"))">
@if ( LayoutType == "sider-with-header-on-top" )
{
<Icon Name="IconName.CheckCircle" TextColor="TextColor.Success" />
}
Sider with Header on top
</BarDropdownItem>
</BarDropdownMenu>
</BarDropdown>
</BarItem>
</BarStart>
<BarEnd>
<BarItem>
<BarDropdown RightAligned>
<BarDropdownToggle><Icon Name="IconName.Language" /></BarDropdownToggle>
<BarDropdownMenu>
@foreach ( var cultureInfo in LocalizationService!.AvailableCultures )
{
<BarDropdownItem @key="@cultureInfo.Name" Clicked="@(()=>SelectCulture(cultureInfo.Name))">
@if ( cultureInfo.IsNeutralCulture )
{
@cultureInfo.EnglishName
}
else
{
@cultureInfo.Parent.EnglishName
}
</BarDropdownItem>
}
</BarDropdownMenu>
</BarDropdown>
</BarItem>
<BarItem>
<BarDropdown RightAligned>
<BarDropdownToggle><Icon Name="IconName.Tint" /> Theme</BarDropdownToggle>
<BarDropdownMenu Style="padding: 15px; min-width:550px;">
<Row>
<Column Margin="Margin.Is2.FromBottom">
<Field>
<Switch TValue="bool" Value="@(Theme?.Enabled == true)" ValueChanged="@ThemeEnabledChanged">Theme enabled</Switch>
</Field>
</Column>
</Row>
<Row>
<Column Margin="Margin.Is2.FromBottom">
<Field>
<Check TValue="bool" Value="@(Theme?.IsGradient == true)" ValueChanged="@ThemeGradientChanged">Gradient colors</Check>
</Field>
<Field>
<Check TValue="bool" Value="@(Theme?.IsRounded == true)" ValueChanged="@ThemeRoundedChanged">Rounded elements</Check>
</Field>
</Column>
</Row>
<Row>
<Column>
<Container Fluid>
<ThemeColorSelector Value="@(Theme?.ColorOptions?.Primary)" ValueChanged="@ThemeColorChanged"></ThemeColorSelector>
</Container>
</Column>
</Row>
</BarDropdownMenu>
</BarDropdown>
</BarItem>
<BarItem>
<BarLink To="https://github.com/Megabit/Blazorise">GitHub</BarLink>
</BarItem>
</BarEnd>
</BarMenu>
</Bar>
@code {
protected override async Task OnInitializedAsync()
{
await SelectCulture( "en-US" );
await base.OnInitializedAsync();
}
Task SelectCulture( string name )
{
LocalizationService!.ChangeLanguage( name );
return Task.CompletedTask;
}
private bool topbarVisible = false;
Task OnLayoutTypeChecked( string layoutType )
{
LayoutType = layoutType;
return LayoutTypeChanged.InvokeAsync( layoutType );
}
[Parameter] public EventCallback<bool> ThemeEnabledChanged { get; set; }
[Parameter] public EventCallback<bool> ThemeGradientChanged { get; set; }
[Parameter] public EventCallback<bool> ThemeRoundedChanged { get; set; }
[Parameter] public EventCallback<string> ThemeColorChanged { get; set; }
[Parameter] public string? LayoutType { get; set; }
[Parameter] public EventCallback<string> LayoutTypeChanged { get; set; }
[Inject] protected ITextLocalizerService? LocalizationService { get; set; }
[CascadingParameter] protected Theme? Theme { get; set; }
}
@@ -0,0 +1,68 @@
using Microsoft.AspNetCore.Components;
namespace Visor.Components.TodoApp
{
public abstract class BaseTodoItems : ComponentBase
{
protected Validations? validations;
protected string? description;
protected Filter filter = Filter.All;
protected List<Todo> todos = new()
{
new() { Description = "Buy milk" },
new() { Description = "Call John regarding the meeting" },
new() { Description = "Walk a dog" },
};
protected IEnumerable<Todo> Todos
{
get
{
var query = from t in todos select t;
if (filter == Filter.Active)
query = from q in query where !q.Completed select q;
if (filter == Filter.Completed)
query = from q in query where q.Completed select q;
return query;
}
}
protected void SetFilter(Filter filter)
{
this.filter = filter;
}
protected void OnCheckAll(bool isChecked)
{
todos.ForEach(x => x.Completed = isChecked);
}
protected async Task OnAddTodo()
{
if (await validations!.ValidateAll())
{
todos.Add(new() { Description = description });
description = null;
await validations.ClearAll();
}
}
protected void OnClearCompleted()
{
todos.RemoveAll(x => x.Completed);
filter = Filter.All;
}
protected Task OnTodoStatusChanged(bool isChecked)
{
return InvokeAsync(StateHasChanged);
}
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace Visor.Components.TodoApp
{
public enum Filter
{
All,
Active,
Completed,
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace Visor.Components.TodoApp
{
public class Todo
{
public bool Completed { get; set; }
public string? Description { get; set; }
}
}
@@ -0,0 +1,25 @@
@if (Todo is not null)
{
<ListGroupItem>
<Field Horizontal Padding="Padding.IsAuto.OnAll">
<FieldBody ColumnSize="ColumnSize.Is1">
<Check TValue="bool" Value="@Todo.Completed" ValueChanged="@OnCheckedChanged"></Check>
</FieldBody>
<FieldBody ColumnSize="ColumnSize.Is11">
@Todo.Description
</FieldBody>
</Field>
</ListGroupItem>
}
@code {
Task OnCheckedChanged(bool isChecked)
{
Todo!.Completed = isChecked;
return StatusChanged?.Invoke(isChecked) ?? Task.CompletedTask;
}
[Parameter] public Todo? Todo { get; set; }
[Parameter] public Func<bool, Task>? StatusChanged { get; set; }
}
@@ -0,0 +1,57 @@
@inherits BaseTodoItems
<Container Fluid>
<Row>
<Column>
<Card>
<CardHeader Padding="Padding.Is1.FromBottom">
<CardTitle Size="HeadingSize.Is4">Todo List</CardTitle>
</CardHeader>
<CardBody Padding="Padding.Is0.FromBottom">
<Fields>
<Column ColumnSize="ColumnSize.Is1">
<Check TValue="bool" Value="@Todos.All(x=>x.Completed)" ValueChanged="@OnCheckAll">All</Check>
</Column>
<Column ColumnSize="ColumnSize.Is11">
<Addons>
<Addon AddonType="AddonType.Body">
<Validations @ref="validations" Mode="ValidationMode.Manual">
<Validation Validator="@ValidationRule.IsNotEmpty">
<TextInput @bind-Value="@description" Placeholder="What needs to be done?"></TextInput>
</Validation>
</Validations>
</Addon>
<Addon AddonType="AddonType.End">
<Button Color="Color.Primary" Clicked="@OnAddTodo">
<Icon Name="IconName.Add" />Add
</Button>
</Addon>
</Addons>
</Column>
</Fields>
</CardBody>
<CardBody Padding="Padding.Is0.OnY">
<ListGroup Flush>
@foreach ( var todo in Todos )
{
<TodoItem Todo="@todo" StatusChanged="@OnTodoStatusChanged" />
}
</ListGroup>
</CardBody>
<CardFooter Padding="Padding.Is3.FromBottom">
<Field Horizontal>
<FieldBody ColumnSize="ColumnSize.Is10">
<Buttons Role="ButtonsRole.Addons">
<Button Color="Color.Info" Clicked="@(() => SetFilter( Filter.All ))" Active="@(filter == Filter.All)">All</Button>
<Button Color="Color.Info" Clicked="@(() => SetFilter( Filter.Active ))" Active="@(filter == Filter.Active)">Active</Button>
<Button Color="Color.Info" Clicked="@(() => SetFilter( Filter.Completed ))" Active="@(filter == Filter.Completed)">Completed</Button>
</Buttons>
</FieldBody>
<FieldBody ColumnSize="ColumnSize.Is2">
<Button Color="Color.Warning" Float="Float.End" Clicked="@OnClearCompleted" Display="@(todos.Any(x=>x.Completed) ? Display.Always : Display.None)">Clear Completed</Button>
</FieldBody>
</Field>
</CardFooter>
</Card>
</Column>
</Row>
</Container>
+2
View File
@@ -0,0 +1,2 @@
global using Blazorise;
global using Blazorise.DataGrid;
+25
View File
@@ -0,0 +1,25 @@
@using Visor.Components.Layout
@inherits LayoutComponentBase
<Layout Sider>
<LayoutSider>
<LayoutSiderContent>
<SideMenu />
</LayoutSiderContent>
</LayoutSider>
<Layout>
@* <LayoutHeader Fixed>
<TopMenu ThemeEnabledChanged="@OnThemeEnabledChanged"
ThemeGradientChanged="@OnThemeGradientChanged"
ThemeRoundedChanged="@OnThemeRoundedChanged"
ThemeColorChanged="@OnThemeColorChanged"
@bind-LayoutType="@layoutType" />
</LayoutHeader> *@
<LayoutContent Padding="Padding.Is4.OnX.Is4.FromTop">
@Body
</LayoutContent>
</Layout>
</Layout>
+89
View File
@@ -0,0 +1,89 @@
using Blazorise;
using Blazorise.Localization;
using Microsoft.AspNetCore.Components;
namespace Visor.Layouts
{
public partial class MainLayout
{
[Inject] protected ITextLocalizerService? LocalizationService { get; set; }
[CascadingParameter] protected Theme? Theme { get; set; }
protected string layoutType = "fixed-header";
protected override async Task OnInitializedAsync()
{
await SelectCulture("en-US");
await base.OnInitializedAsync();
}
private Task SelectCulture(string name)
{
LocalizationService!.ChangeLanguage(name);
return Task.CompletedTask;
}
Task OnThemeEnabledChanged(bool value)
{
if (Theme is null)
return Task.CompletedTask;
Theme.Enabled = value;
return InvokeAsync(Theme.ThemeHasChanged);
}
Task OnThemeGradientChanged(bool value)
{
if (Theme is null)
return Task.CompletedTask;
Theme.IsGradient = value;
return InvokeAsync(Theme.ThemeHasChanged);
}
Task OnThemeRoundedChanged(bool value)
{
if (Theme is null)
return Task.CompletedTask;
Theme.IsRounded = value;
return InvokeAsync(Theme.ThemeHasChanged);
}
Task OnThemeColorChanged(string value)
{
if (Theme is null)
return Task.CompletedTask;
Theme.ColorOptions ??= new();
Theme.BackgroundOptions ??= new();
Theme.TextColorOptions ??= new();
Theme.ColorOptions.Primary = value;
Theme.BackgroundOptions.Primary = value;
Theme.TextColorOptions.Primary = value;
Theme.InputOptions ??= new();
Theme.InputOptions.CheckColor = value;
Theme.InputOptions.SliderColor = value;
Theme.SpinKitOptions ??= new();
Theme.SpinKitOptions.Color = value;
return InvokeAsync(Theme.ThemeHasChanged);
}
}
}
+38
View File
@@ -0,0 +1,38 @@
namespace Visor.Models
{
public class AllPubData
{
public static List<ObjectItems> ListObjectItems { get; set; } = new();
public static List<DeviceItems> ListDeviceItems { get; set; } = new();
public static List<DeviceTypeItems> ListDeviceTypeItems { get; set; } = new();
public static HttpClient http { get; set; } = new()
{
BaseAddress = new Uri("http://localhost:5262/")
};
public static Task GetAll() => _GetAllData();
public static Task GetObjects() => _GetObject();
public static Task GetType() => _GetType();
public static Task GetDevice() => _GetDevice();
private static async Task _GetAllData()
{
var o = await _GetObject();
var t = await _GetType();
var d = await _GetDevice();
}
private static async Task<List<ObjectItems>> _GetObject()
{
return ListObjectItems = await http.GetFromJsonAsync<List<ObjectItems>>("api/Object/GetObject");
}
private static async Task<List<DeviceItems>> _GetDevice()
{
return ListDeviceItems = http.GetFromJsonAsync<List<DeviceItems>>("api/Device/GetDevices").Result.Take(100).ToList();
}
private static async Task<List<DeviceTypeItems>> _GetType()
{
return ListDeviceTypeItems = await http.GetFromJsonAsync<List<DeviceTypeItems>>("api/Type/GetTypes");
}
}
}
+27
View File
@@ -0,0 +1,27 @@
namespace Visor.Models
{
public class DeviceItems
{
public Guid Id { get; set; }
public string? Ip { get; set; }
public string? Mac { get; set; }
public string? Description { get; set; }
public string? Name { get; set; }
public string? Location { get; set; }
public List<DeviceItems>? ListDevices { get; set; }
public DeviceTypeItems? DeviceType { get; set; }
public ObjectItems? DeviceObject { get; set; }
public Guid ConnectedToSwitchId { get; set; }
public int ConnectedToPortOnSwitch { get; set; }
}
//Unknown = 0,
//SwitchCore = 1,
//SwitchAccess = 2,
//Camera = 3,
//Server = 4,
//WorkStation = 5,
//PDU_AVR = 6,
}
+9
View File
@@ -0,0 +1,9 @@
namespace Visor.Models
{
public class DeviceTypeItems
{
public Guid Id { get; set; }
public string Name { get; set; }
public string? Description { get; set; } = null;
}
}
+12
View File
@@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
namespace Visor.Models
{
public class ObjectItems
{
[Key]
public Guid Id { get; set; }
public string NameObject { get; set; } = "Unknown";
public string DescriptionObject { get; set; } = string.Empty;
}
}
+5
View File
@@ -0,0 +1,5 @@
@page "/"
@using Visor.Models
@inject IVersionProvider VersionProvider
<Heading Size="HeadingSize.Is1" Margin="Margin.Is3.FromBottom">ДАШБОРД</Heading>
+37
View File
@@ -0,0 +1,37 @@
@page
@model Visor.Pages.ErrorModel
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Error</title>
</head>
<body>
<h1>Error.</h1>
<h2>An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
</body>
</html>
+27
View File
@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Diagnostics;
namespace Visor.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}
@@ -0,0 +1,179 @@
@page "/setting/add_objects"
@using Visor.Models
@* @inject HttpClient Http *@
<LoadingIndicator Visible="@isLoading" FullScreen />
<Row Width="Width.Max100">
<Column ColumnSize="ColumnSize.Is10"></Column>
<Column ColumnSize="ColumnSize.Is2">
<Button Color="Color.Primary" Clicked="@ShowAddObjetc">
<Icon Name="IconName.Add" />
</Button>
<Button Color="Color.Primary" Outline Clicked="@ShowDelObjetc">
<Icon Name="IconName.Delete" />
</Button>
</Column>
</Row>
<Divider />
<Div>
@if (isLoading || listObjects is null)
{
<Paragraph>@MessagePages</Paragraph>
}
else if (listObjects != null && listObjects.Any())
{
<Table>
<TableHeader>
<TableRow>
<TableHeaderCell>Название</TableHeaderCell>
<TableHeaderCell>Описание</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
@foreach (var item in listObjects)
{
<TableRow>
<TableRowCell>@item.NameObject</TableRowCell>
<TableRowCell>@item.DescriptionObject</TableRowCell>
</TableRow>
}
</TableBody>
</Table>
}
else
{
<Alert Color="Color.Warning">@MessagePages</Alert>
<Paragraph>@MessagePages</Paragraph>
}
</Div>
@code {
private List<ObjectItems>? listObjects = AllPubData.ListObjectItems;
private Modal modalAdd;
private Modal modalDel;
private Guid select_id { get; set; }
private int id_object { get; set; }
private string name_object { get; set; }
private string decript_object { get; set; }
private HttpClient Http = AllPubData.http;
private string? MessagePages;
bool isLoading = true;
protected override async Task OnInitializedAsync()
{
await AllPubData.GetObjects();
listObjects = AllPubData.ListObjectItems;
if (listObjects.Count == 0)
MessagePages = "Нет данных";
isLoading = false;
}
private Task ShowAddObjetc()
{
id_object = listObjects.Count;
return modalAdd.Show();
}
private async Task AddObject()
{
var obj = new ObjectItems()
{
NameObject = name_object,
DescriptionObject = decript_object
};
var post = await Http.PostAsJsonAsync("api/Object/AddObject", obj);
if (post.IsSuccessStatusCode)
{
listObjects.Add(obj);
await modalAdd.Hide();
//await AllPubData.ReloadObject();
}
}
private Task ShowDelObjetc() => modalDel.Show();
private async Task DelObject()
{
var del = await Http.DeleteAsync($"api/Object/DelObject?id={select_id}");
if (del.IsSuccessStatusCode)
{
listObjects.Remove(listObjects.Where(i => i.Id == select_id).First());
await HideDelModal();
}
}
private Task HideAddModal() => modalAdd.Hide();
private Task HideDelModal() => modalDel.Hide();
}
<Modal @ref="modalAdd" Centered>
<ModalContent>
<ModalHeader>
<ModalTitle>Добавить объект</ModalTitle>
</ModalHeader>
<ModalBody>
@* <Addons>
<Addon AddonType="AddonType.Start">
<AddonLabel>Id</AddonLabel>
</Addon>
<Addon AddonType="AddonType.Body">
<NumericInput Disabled Placeholder="Номер объекта" @bind-Value="@id_object" />
</Addon>
</Addons> *@
<Addons>
<Addon AddonType="AddonType.Start">
<AddonLabel>Name</AddonLabel>
</Addon>
<Addon AddonType="AddonType.Body">
<TextInput @bind-Value="@name_object" Placeholder="Имя объекта" />
</Addon>
</Addons>
<Addons>
<Addon AddonType="AddonType.Start">
<AddonLabel>Описание</AddonLabel>
</Addon>
<Addon AddonType="AddonType.Body">
<TextInput Placeholder="Дополнительная информация" @bind-Value="@decript_object" />
</Addon>
</Addons>
<ModalFooter>
<Button Color="Color.Secondary" Clicked="@HideAddModal">Close</Button>
<Button Color="Color.Primary" Clicked="@AddObject">Добавить</Button>
</ModalFooter>
</ModalBody>
</ModalContent>
</Modal>
<Modal @ref="modalDel" Centered>
<ModalContent>
<ModalHeader>
<ModalTitle>Удалить объект</ModalTitle>
</ModalHeader>
<ModalBody>
<Addons>
<Addon AddonType="AddonType.Start">
<AddonLabel>Id</AddonLabel>
</Addon>
<Addon AddonType="AddonType.Body">
<Select @bind-Value="@select_id">
<SelectItem Value="9999">Объект</SelectItem>
@foreach (var item in listObjects)
{
<SelectItem Value="@item.Id">@item.NameObject</SelectItem>
}
</Select>
</Addon>
</Addons>
<ModalFooter>
<Button Color="Color.Secondary" Clicked="@HideDelModal">Close</Button>
<Button Color="Color.Primary" Clicked="@DelObject">Удалить</Button>
</ModalFooter>
</ModalBody>
</ModalContent>
</Modal>
+466
View File
@@ -0,0 +1,466 @@
@page "/setting/wizard"
@using Visor.Models
@using System.Text.Json
<h3>Wizard</h3>
<Toaster>
<Toast @bind-Visible="@toastError">
<ToastHeader>
<Strong Margin="Margin.IsAuto.FromEnd">@error_group</Strong>
<CloseButton />
</ToastHeader>
<ToastBody>
@error_msg
</ToastBody>
</Toast>
</Toaster>
<LoadingIndicator @bind-Visible="@visible">
<Steps @ref="stepsRef" @bind-SelectedStep="@selectedStep" NavigationAllowed="NavigationAllowed">
<Items>
<Step Name="1">Объекты</Step>
<Step Name="2">Типы</Step>
<Step Name="3">Устройства</Step>
<Step Name="4">Итог</Step>
</Items>
<Content>
<StepPanel Name="1">
<Row>
<Column ColumnSize="ColumnSize.Is3">
<Card>
<CardBody>
<CardText>
<Field>
<TextInput Autofocus="true" @bind-Value="obj_name" Placeholder="Название объекта">
<FieldHelp>Введите название объекта. Например: VNK, SPB...</FieldHelp>
</TextInput>
</Field>
<Field>
<TextInput @bind-Value="obj_desc" Placeholder="Описание объекта">
<FieldHelp>Введите текстовое описание объекта</FieldHelp>
</TextInput>
</Field>
<Field>
<Button Color="Color.Primary" Clicked="@AddNewObject">
<Icon Name="IconName.Add" />
</Button>
</Field>
</CardText>
</CardBody>
</Card>
</Column>
<Column ColumnSize="ColumnSize.Is9">
<Card>
<CardBody>
<CardText>
<Table>
<TableHeader>
<TableRow>
<TableHeaderCell>Название объекта</TableHeaderCell>
<TableHeaderCell>Описание объекта</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
@foreach (var item in _list_obj)
{
<TableRow>
<TableRowCell>@item.NameObject</TableRowCell>
<TableRowCell>@item.DescriptionObject</TableRowCell>
</TableRow>
}
</TableBody>
</Table>
</CardText>
</CardBody>
</Card>
</Column>
</Row>
</StepPanel>
<StepPanel Name="2">
<Row>
<Column ColumnSize="ColumnSize.Is3">
<Card>
<CardBody>
<CardText>
<Field>
<TextInput Autofocus="true" @bind-Value="type_name" Placeholder="Название типа устройства">
<FieldHelp>Введите название типа. Например: Server, Switch...</FieldHelp>
</TextInput>
</Field>
<Field>
<TextInput @bind-Value="type_desc" Placeholder="Описание типа">
<FieldHelp>Введите текстовое описание типа устройства</FieldHelp>
</TextInput>
</Field>
<Field>
<Button Color="Color.Primary" Clicked="@AddNewType">
<Icon Name="IconName.Add" />
</Button>
</Field>
</CardText>
</CardBody>
</Card>
</Column>
<Column ColumnSize="ColumnSize.Is9">
<Card>
<CardBody>
<CardText>
<Table>
<TableHeader>
<TableRow>
<TableHeaderCell>Тип устройства</TableHeaderCell>
<TableHeaderCell>Описание типа устройств</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
@foreach (var item in _list_type)
{
<TableRow>
<TableRowCell>@item.Name</TableRowCell>
<TableRowCell>@item.Description</TableRowCell>
</TableRow>
}
</TableBody>
</Table>
</CardText>
</CardBody>
</Card>
</Column>
</Row>
</StepPanel>
<StepPanel Name="3">
<Row>
<Column ColumnSize="ColumnSize.Is3">
<Card>
<CardBody>
<CardTitle Size="HeadingSize.Is2">
Добавление устройства
</CardTitle>
<CardText>
<Addons Size="Size.Small">
<Addon AddonType="AddonType.Start">
<AddonLabel Width="Width.Is25">IP</AddonLabel>
</Addon>
<Addon AddonType="AddonType.Body">
<InputMask @bind-Value="dev_ip" Width="Width.Is50" Mask="999.999.999.999" MaskPlaceholder="X" Placeholder="Введите IP" />
</Addon>
</Addons>
<Addons Size="Size.Small">
<Addon AddonType="AddonType.Start">
<AddonLabel Width="Width.Is25">MAC</AddonLabel>
</Addon>
<Addon AddonType="AddonType.Body">
<InputMask @bind-Value="dev_mac" Width="Width.Is50" Mask="****-****-****" MaskPlaceholder="X" Placeholder="Введите MAC" />
</Addon>
</Addons>
<Addons Size="Size.Small">
<Addon AddonType="AddonType.Start">
<AddonLabel Width="Width.Is25">Название</AddonLabel>
</Addon>
<Addon AddonType="AddonType.Body">
<TextInput @bind-Value="dev_name" Width="Width.Is50" Placeholder="Введите название устройства" />
</Addon>
</Addons>
<Addons Size="Size.Small">
<Addon AddonType="AddonType.Start">
<AddonLabel Width="Width.Is25">Описание</AddonLabel>
</Addon>
<Addon AddonType="AddonType.Body">
<TextInput @bind-Value="dev_desc" Width="Width.Is50" Placeholder="Введите описание устройства" />
</Addon>
</Addons>
<Addons Size="Size.Small">
<Addon AddonType="AddonType.Start">
<AddonLabel Width="Width.Is25">Место установки</AddonLabel>
</Addon>
<Addon AddonType="AddonType.Body">
<TextInput @bind-Value="dev_loc" Width="Width.Is50" Placeholder="Введите где установлено устройство" />
</Addon>
</Addons>
<Addons Size="Size.Small">
<Addon AddonType="AddonType.Start">
<AddonLabel Width="Width.Is25">Тип устройства</AddonLabel>
</Addon>
<Addon AddonType="AddonType.Body">
<Select Width="Width.Is50" TValue="Guid" @bind-Value="@id_type">
<SelectItem Value="999">Тип устройства</SelectItem>
@foreach (var sel in _list_type)
{
<SelectItem Value="@sel.Id">@sel.Name</SelectItem>
}
</Select>
</Addon>
</Addons>
<Addons Size="Size.Small">
<Addon AddonType="AddonType.Start">
<AddonLabel Width="Width.Is25">Объект</AddonLabel>
</Addon>
<Addon AddonType="AddonType.Body">
<Select Width="Width.Is50" TValue="Guid" @bind-Value="@id_obj">
<SelectItem Value="999">На объекте</SelectItem>
@foreach (var sel in _list_obj)
{
<SelectItem Value="@sel.Id">@sel.NameObject</SelectItem>
}
</Select>
</Addon>
</Addons>
<Addons Size="Size.Small">
<Addon AddonType="AddonType.Start">
<AddonLabel Width="Width.Is25">Подключен к</AddonLabel>
</Addon>
<Addon AddonType="AddonType.Body">
<Select Width="Width.Is50" TValue="Guid" @bind-Value="@id_dev">
<SelectItem Value="999">коммутатор</SelectItem>
@foreach (var sel in _list_switch)
{
<SelectItem Value="@sel.Id">@sel.Name</SelectItem>
}
</Select>
</Addon>
<Addon AddonType="AddonType.End">
<Button Clicked="@RefreshListSwitch">
<Icon Name="IconName.Undo" />
</Button>
</Addon>
</Addons>
<Addons Size="Size.Small">
<Addon AddonType="AddonType.Start">
<AddonLabel Width="Width.Is25">Порт подключения</AddonLabel>
</Addon>
<Addon AddonType="AddonType.Body">
<NumericInput @bind-Value="num_port" Min="1" Max="28" Width="Width.Is50" />
</Addon>
</Addons>
<Button Width="Width.Is100" Color="Color.Dark" Clicked="@AddNewDev">Добавить</Button>
</CardText>
</CardBody>
</Card>
</Column>
<Column ColumnSize="ColumnSize.Is9">
<Card>
<CardBody>
<CardText>
<Table>
<TableHeader>
<TableRow>
<TableHeaderCell>Имя</TableHeaderCell>
<TableHeaderCell>Адрес</TableHeaderCell>
<TableHeaderCell>Тип</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
@foreach (var item in _list_dev)
{
<TableRow>
<TableRowCell>@item.Name</TableRowCell>
<TableRowCell>@item.Ip</TableRowCell>
<TableRowCell>@item.DeviceType.Name</TableRowCell>
</TableRow>
}
</TableBody>
</Table>
</CardText>
</CardBody>
</Card>
</Column>
</Row>
</StepPanel>
<StepPanel Name="4">
<Row>
<CardDeck>
<Card>
<CardBody>
<CardTitle TextAlignment="TextAlignment.Center">
ОБЪЕКТЫ
</CardTitle>
<CardText>
Объектов к добавлению - @_list_obj.Count
</CardText>
</CardBody>
</Card>
<Card>
<CardBody>
<CardTitle TextAlignment="TextAlignment.Center">
ТИПЫ УСТРОЙСТВ
</CardTitle>
<CardText>
Типов к добавлению - @_list_type.Count
</CardText>
</CardBody>
</Card>
<Card>
<CardBody>
<CardTitle TextAlignment="TextAlignment.Center">
УСТРОЙСТВА
</CardTitle>
<CardText>
Устройств к добавлению - @_list_dev.Count
из них коммутаторов - @_list_switch.Count
</CardText>
</CardBody>
</Card>
</CardDeck>
</Row>
</StepPanel>
</Content>
</Steps>
<Div Flex="Flex.JustifyContent.Center">
<Button Color="Color.Secondary" Margin="Margin.Is2.FromEnd" Clicked="() => stepsRef.PreviousStep()">
Назад
</Button>
<Button Color="Color.Primary" Clicked="clickSave">
@nextButton
</Button>
</Div>
</LoadingIndicator>
<Progress Visibility="@visibleProgress" Indeterminate />
@code {
bool visible, toastError = false;
Visibility visibleProgress = Visibility.Invisible;
private Steps stepsRef;
private ObjectItems obj_item;
private DeviceTypeItems type_item;
private DeviceItems dev_item;
private List<ObjectItems> list_obj = new(), _list_obj = new();
private List<DeviceTypeItems> list_type = new(), _list_type = new();
private static List<DeviceItems> list_dev = new(), _list_dev = new();
private List<DeviceItems> _list_switch = new();
private string email, nextButton = "Далее";
private string selectedStep = "1";
private Guid id_type, id_obj, id_dev;
private int num_port;
private string obj_name, obj_desc;
private string type_name, type_desc;
private string error_msg, error_group;
private string dev_ip, dev_mac, dev_desc, dev_name, dev_loc;
protected override async Task OnInitializedAsync()
{
visible = true;
await AllPubData.GetAll();
list_type = AllPubData.ListDeviceTypeItems;
list_obj = AllPubData.ListObjectItems;
list_dev = AllPubData.ListDeviceItems;
visible = false;
}
private Task<bool> NavigationAllowed(StepNavigationContext context)
{
if (context.CurrentStepIndex == 3 && context.NextStepIndex == 4)
{
nextButton = "Сохранить";
//return Task.FromResult(true);
}
if (context.CurrentStepIndex == 4 && context.NextStepIndex > 4)
{
//return Task.FromResult(false);
}
if (context.CurrentStepIndex == 4 && context.NextStepIndex < 4)
{
nextButton = "Далее";
}
return Task.FromResult(true);
}
private void RefreshListSwitch()
{
_list_switch = _list_dev.Where(s => s.DeviceType.Name.Contains("Switch")).ToList();
}
private async void AddNewObject()
{
obj_item = new()
{
Id = Guid.NewGuid(),
NameObject = obj_name,
DescriptionObject = obj_desc,
};
_list_obj.Add(obj_item);
obj_name = string.Empty;
obj_desc = string.Empty;
}
private async void AddNewType()
{
type_item = new()
{
Id = Guid.NewGuid(),
Name = type_name,
Description = type_desc,
};
_list_type.Add(type_item);
type_name = string.Empty;
type_desc = string.Empty;
}
private async void AddNewDev()
{
dev_item = new()
{
Id = Guid.NewGuid(),
Ip = dev_ip,
Mac = dev_mac,
Description = dev_desc,
Name = dev_name,
Location = dev_loc,
DeviceType = _list_type.SingleOrDefault(t => t.Id == id_type),
DeviceObject = _list_obj.SingleOrDefault(o => o.Id == id_obj),
ConnectedToPortOnSwitch = num_port
};
_list_dev.Add(dev_item);
if (dev_item.DeviceType.Name.Contains("Switch")) _list_switch.Add(dev_item);
dev_ip = string.Empty;
dev_mac = string.Empty;
dev_desc = string.Empty;
dev_name = string.Empty;
dev_loc = string.Empty;
}
private async void clickSave()
{
if (nextButton == "Далее") stepsRef.NextStep();
else
{
visibleProgress = Visibility.Visible;
foreach (var item in _list_obj)
{
var p = await AllPubData.http.PostAsJsonAsync("api/Object/AddObject", item);
if (!p.IsSuccessStatusCode)
{
error_group = "ОБЪЕКТ";
error_msg = $"{item.NameObject} не добавлено.\r\nСтатус код: {p.StatusCode}";
toastError = true;
}
}
foreach (var item in _list_type)
{
var p = await AllPubData.http.PostAsJsonAsync("api/Type/AddType", item);
if (!p.IsSuccessStatusCode)
{
error_group = "ТИП";
error_msg = $"{item.Name} не добавлено.\r\nСтатус код: {p.StatusCode}";
toastError = true;
}
}
foreach (var item in _list_dev)
{
var p = await AllPubData.http.PostAsJsonAsync("api/Device/AddDevice", item);
if (!p.IsSuccessStatusCode)
{
error_group = "УСТРОЙСТВА";
error_msg = $"{item.Name} не добавлено.\r\nСтатус код: {p.StatusCode}";
toastError = true;
}
}
visibleProgress = Visibility.Invisible;
}
}
}
@@ -0,0 +1,75 @@
@page "/simple-datagrid"
<DataGrid TItem="Book"
Data="data"
Editable
Sortable
SortMode="DataGridSortMode.Single"
ShowPager
ShowPageSizes
Resizable
Responsive
Bordered
Hoverable
Narrow
Groupable
ShowGrouping
ResizeMode="TableResizeMode.Columns"
NewItemCreator="() => new Book(string.Empty, string.Empty, string.Empty, DateOnly.MinValue, string.Empty)">
<DataGridColumns>
<DataGridColumn Field="@nameof(Book.Title)" Caption="Title" Editable></DataGridColumn>
<DataGridColumn Field="@nameof(Book.Author)" Caption="Author" Editable Groupable></DataGridColumn>
<DataGridColumn Field="@nameof(Book.Isbn)" Caption="Isbn" Editable></DataGridColumn>
<DataGridColumn Field="@nameof(Book.PublicationDate)" Caption="Publication Date" Editable></DataGridColumn>
<DataGridColumn Field="@nameof(Book.Publisher)" Caption="Publisher" Editable></DataGridColumn>
<DataGridCommandColumn PreventRowClick>
<NewCommandTemplate>
<Button Color="Color.Success" Clicked="@context.Clicked">@context.LocalizationString</Button>
</NewCommandTemplate>
<EditCommandTemplate>
<Button Color="Color.Primary" Clicked="@context.Clicked">@context.LocalizationString</Button>
</EditCommandTemplate>
<SaveCommandTemplate>
<Button Type="ButtonType.Submit" PreventDefaultOnSubmit Color="Color.Primary" Clicked="@context.Clicked">@context.LocalizationString</Button>
</SaveCommandTemplate>
<DeleteCommandTemplate>
<Button Color="Color.Danger" Clicked="@context.Clicked">@context.LocalizationString</Button>
</DeleteCommandTemplate>
<CancelCommandTemplate>
<Button Color="Color.Secondary" Clicked="@context.Clicked">@context.LocalizationString</Button>
</CancelCommandTemplate>
<ClearFilterCommandTemplate>
<Button Color="Color.Warning" Clicked="@context.Clicked">@context.LocalizationString</Button>
</ClearFilterCommandTemplate>
</DataGridCommandColumn>
</DataGridColumns>
</DataGrid>
@code {
private List<Book> data = new()
{
new("Harry Potter and the Sorcerer's Stone", "J.K. Rowling","9780590353427", new(1997,6,26), "Scholastic Press" ),
new("To Kill a Mockingbird", "Harper Lee", "9780061120084", new(1960,7,11), "J.B. Lippincott & Co." ),
new("The Catcher in the Rye", "J.D. Salinger","9780316769488", new(1951,7,16), "Little, Brown and Company" ),
new("The Great Gatsby", "F. Scott Fitzgerald","9780743273565", new(1925,4,10), "Charles Scribner's Sons" ),
new("The Lord of the Rings", "J.R.R. Tolkien", "9780544003415", new(1954,7,29), "George Allen & Unwin"),
new("The Hobbit", "J.R.R. Tolkien", "9780547928227", new(1937,9,21), "George Allen & Unwin"),
new("The Chronicles of Narnia: The Lion, the Witch, and the Wardrobe", "C.S. Lewis", "9780064471073", new(1950,10,16), "Geoffrey Bles"),
new("Animal Farm", "George Orwell", "978451219811", new(1945,8,17), "Secker & Warburg"),
new("The Adventures of Huckleberry Finn", "Mark Twain", "9780142437254", new(1884,12,10), "Chatto & Windus"),
new("The Diary of a Young Girl", "Anne Frank", "9780385473788", new(1947,6,25), "Doubleday"),
new("The Hunger Games", "Suzanne Collins", "9780439023481", new(2008,9,14), "Scholastic Press"),
new("The Maze Runner", "James Dashner", "9780385737965", new(2009,10,6), "Delacorte Press"),
new("The Secret Life of Bees", "Sue Monk Kidd", "9780670034852", new(2002,5,5), "Viking Press"),
new("The Strange Case of Dr. Jekyll and Mr. Hyde", "Robert Louis Stevenson", "9781593080171", new(1886,1,5), "Longmans, Green and Co"),
new("Harry Potter and the Chamber of Secrets", "J.K. Rowling", "9780439064866", new(1998,7,2), "Scholastic Press"),
new("Harry Potter and the Prisoner of Azkaban", "J.K. Rowling", "9780439136365", new(1999,7,8), "Scholastic Press"),
new("Catching Fire", "Suzanne Collins", "9780439023498", new(2009,9,1), "Scholastic Press"),
new("Mockingjay", "Suzanne Collins", "9780439023528", new(2010,8,24), "Scholastic Press"),
new("The Children of Hurin", "J.R.R. Tolkien", "9780544003422", new(2007,4,17), "Houghton Mifflin Harcourt"),
new("Beren and Luthien", "J.R.R. Tolkien", "9780544116813", new(2017,5,23), "Houghton Mifflin Harcourt")
};
private record Book( string Title, string Author, string Isbn, DateOnly PublicationDate, string Publisher );
}
+124
View File
@@ -0,0 +1,124 @@
@page "/simple-form"
<Row>
<Column ColumnSize="ColumnSize.Is6.OnDesktop">
<Card Margin="Margin.Is4.FromBottom">
<CardHeader>
<CardTitle>Basic Example</CardTitle>
</CardHeader>
<CardBody>
<Validations @ref="validationsBasicExampleRef" Mode="ValidationMode.Auto" ValidateOnLoad="false">
<Validation Validator="@ValidationRule.IsEmail">
<Field>
<FieldLabel>Email address</FieldLabel>
<TextInput Placeholder="Enter email" />
<FieldHelp>We'll never share your email with anyone else.</FieldHelp>
</Field>
</Validation>
<Validation Validator="@ValidatePassword">
<Field>
<FieldLabel>Password</FieldLabel>
<TextInput Role="TextRole.Password" Placeholder="Password" />
</Field>
</Validation>
<Field>
<Check TValue="bool">Remember me?</Check>
</Field>
<Button Color="Color.Primary" Clicked="SubmitBasicExample">Submit</Button>
</Validations>
</CardBody>
</Card>
</Column>
<Column ColumnSize="ColumnSize.Is6.OnDesktop">
<Card Margin="Margin.Is4.FromBottom">
<CardHeader>
<CardTitle>Horizontal Form</CardTitle>
</CardHeader>
<CardBody>
<Validations @ref="validationsHorizontalFormRef" Mode="ValidationMode.Auto" ValidateOnLoad="false">
<Validation Validator="@ValidationRule.IsEmail">
<Field Horizontal>
<FieldLabel ColumnSize="ColumnSize.IsFull.OnTablet.Is3.OnDesktop">Email address</FieldLabel>
<FieldBody ColumnSize="ColumnSize.IsFull.OnTablet.Is9.OnDesktop">
<TextInput Placeholder="Email" />
</FieldBody>
</Field>
</Validation>
<Validation Validator="@ValidatePassword">
<Field Horizontal>
<FieldLabel ColumnSize="ColumnSize.IsFull.OnTablet.Is3.OnDesktop">Password</FieldLabel>
<FieldBody ColumnSize="ColumnSize.IsFull.OnTablet.Is9.OnDesktop">
<TextInput Role="TextRole.Password" Placeholder="Password" @bind-Value="@password" />
</FieldBody>
</Field>
</Validation>
<Validation Validator="@ValidatePassword2">
<Field Horizontal>
<FieldLabel ColumnSize="ColumnSize.IsFull.OnTablet.Is3.OnDesktop">Re Password</FieldLabel>
<FieldBody ColumnSize="ColumnSize.IsFull.OnTablet.Is9.OnDesktop">
<TextInput Role="TextRole.Password" Placeholder="Retype password" />
</FieldBody>
</Field>
</Validation>
<Field Horizontal JustifyContent="JustifyContent.End">
<FieldBody ColumnSize="ColumnSize.Is9.Is3.WithOffset">
<Check TValue="bool">Remember me?</Check>
</FieldBody>
</Field>
<Field Horizontal JustifyContent="JustifyContent.End">
<FieldBody ColumnSize="ColumnSize.Is9.Is3.WithOffset">
<Button Color="Color.Primary" Clicked="SubmitHorizontalForm">Submit</Button>
</FieldBody>
</Field>
</Validations>
</CardBody>
</Card>
</Column>
</Row>
@code {
Validations? validationsBasicExampleRef;
Validations? validationsHorizontalFormRef;
string? password;
void ValidatePassword( ValidatorEventArgs e )
{
e.Status = Convert.ToString( e.Value )?.Length >= 6 ? ValidationStatus.Success : ValidationStatus.Error;
}
void ValidatePassword2( ValidatorEventArgs e )
{
var password2 = Convert.ToString( e.Value );
if ( password2?.Length < 6 )
{
e.Status = ValidationStatus.Error;
e.ErrorText = "Password must be at least 6 characters long!";
}
else if ( password2 != password )
{
e.Status = ValidationStatus.Error;
}
else
{
e.Status = ValidationStatus.Success;
}
}
async Task SubmitBasicExample()
{
if ( await validationsBasicExampleRef!.ValidateAll() )
{
await validationsBasicExampleRef.ClearAll();
}
}
async Task SubmitHorizontalForm()
{
if ( await validationsHorizontalFormRef!.ValidateAll() )
{
await validationsHorizontalFormRef.ClearAll();
}
}
}
+8
View File
@@ -0,0 +1,8 @@
@page "/"
@namespace Visor.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = "_Layout";
}
<component type="typeof(App)" render-mode="Server" />
+30
View File
@@ -0,0 +1,30 @@
@using Microsoft.AspNetCore.Components.Web
@namespace Visor.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Visor</title>
<base href="~/" />
<component type="typeof(HeadOutlet)" render-mode="Server" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link href="_content/Blazorise.Icons.FontAwesome/v6/css/all.min.css" rel="stylesheet">
<link href="_content/Blazorise/blazorise.min.css?v=2.2.1.0" rel="stylesheet" />
<link href="_content/Blazorise.Bootstrap5/blazorise.bootstrap5.min.css?v=2.2.1.0" rel="stylesheet" />
<link href="Visor.Styles.css" rel="stylesheet" />
<link rel="stylesheet" href="css/blazor-ui.css" />
</head>
<body>
@RenderBody()
<script src="_framework/blazor.server.js"></script>
</body>
</html>
+41
View File
@@ -0,0 +1,41 @@
using Blazorise.Bootstrap5;
using Blazorise.Icons.FontAwesome;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
AddBlazorise(builder.Services);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
void AddBlazorise(IServiceCollection services)
{
services
.AddBlazorise();
services
.AddBootstrap5Providers()
.AddFontAwesomeIcons();
}
@@ -0,0 +1,28 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:61646",
"sslPort": 44332
}
},
"profiles": {
"Visor": {
"commandName": "Project",
"dotnetRunMessages": false,
"launchBrowser": true,
"applicationUrl": "https://localhost:7297;http://localhost:5255",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
+31
View File
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>2.2.1</Version>
<Authors>Megabit</Authors>
<Company>Megabit</Company>
<Description>
This is a Blazorise Template for a Blazor Server Application.
Blazorise is a component library built on top of Blazor and CSS frameworks like Bootstrap, FluentUI2, Tailwind, Bulma and Material.
</Description>
<Copyright>Copyright 2018-2026 Megabit</Copyright>
</PropertyGroup>
<ItemGroup>
<None Remove=".mcp.json" />
<None Include=".mcp.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Blazorise" Version="2.2.1" />
<PackageReference Include="Blazorise.Bootstrap5" Version="2.2.1" />
<PackageReference Include="Blazorise.Icons.FontAwesome" Version="2.2.1" />
<PackageReference Include="Blazorise.DataGrid" Version="2.2.1" />
<PackageReference Include="Blazorise.LoadingIndicator" Version="2.2.1" />
<PackageReference Include="Blazorise.SpinKit" Version="2.2.1" />
</ItemGroup>
</Project>
+14
View File
@@ -0,0 +1,14 @@
@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using Visor
@using Blazorise
@using Blazorise.SpinKit
@using Blazorise.LoadingIndicator
@using Blazorise.DataGrid
+9
View File
@@ -0,0 +1,9 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

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