diff --git a/LoongPanel-Asp.sln b/LoongPanel-Asp.sln index 6aa2584..6d3584a 100755 --- a/LoongPanel-Asp.sln +++ b/LoongPanel-Asp.sln @@ -2,8 +2,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoongPanel-Asp", "LoongPanel-Asp\LoongPanel-Asp.csproj", "{3AED83DF-9EF2-4AC6-BDBC-6E1C9D823405}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pty.Net", "PtyTerminal\Pty.Net\Pty.Net.csproj", "{FEE728E2-BD7E-49C5-BC52-A9AE66D84DD5}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -14,9 +12,5 @@ Global {3AED83DF-9EF2-4AC6-BDBC-6E1C9D823405}.Debug|Any CPU.Build.0 = Debug|Any CPU {3AED83DF-9EF2-4AC6-BDBC-6E1C9D823405}.Release|Any CPU.ActiveCfg = Release|Any CPU {3AED83DF-9EF2-4AC6-BDBC-6E1C9D823405}.Release|Any CPU.Build.0 = Release|Any CPU - {FEE728E2-BD7E-49C5-BC52-A9AE66D84DD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FEE728E2-BD7E-49C5-BC52-A9AE66D84DD5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FEE728E2-BD7E-49C5-BC52-A9AE66D84DD5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FEE728E2-BD7E-49C5-BC52-A9AE66D84DD5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/LoongPanel-Asp.sln.DotSettings.user b/LoongPanel-Asp.sln.DotSettings.user index 6fb4815..c1ff35d 100755 --- a/LoongPanel-Asp.sln.DotSettings.user +++ b/LoongPanel-Asp.sln.DotSettings.user @@ -1,4 +1,5 @@  + True <AssemblyExplorer> <Assembly Path="C:\Users\niyyz\.nuget\packages\czgl.systeminfo\2.2.0\lib\net7.0\CZGL.SystemInfo.dll" /> </AssemblyExplorer> \ No newline at end of file diff --git a/LoongPanel-Asp/ApplicationDbContext.cs b/LoongPanel-Asp/ApplicationDbContext.cs index f23ab78..b73234e 100755 --- a/LoongPanel-Asp/ApplicationDbContext.cs +++ b/LoongPanel-Asp/ApplicationDbContext.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Security.Cryptography; using System.Text.Json; using LoongPanel_Asp.Models; using LoongPanel_Asp.utils; @@ -88,7 +89,7 @@ public class ApplicationDbContext(DbContextOptions options InitializeRoles(modelBuilder); } - private static void InitializeRoles(ModelBuilder modelBuilder) + private void InitializeRoles(ModelBuilder modelBuilder) { string[] roleNames = ["Admin", "User"]; @@ -114,12 +115,11 @@ public class ApplicationDbContext(DbContextOptions options Name = roleName, NormalizedName = roleName.ToUpperInvariant(), ApiPermissions = - ["1","2,","3","4","5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20"], - RouterPermissions = ["1", "2", "3", "4"] + ["1","2","3","4","5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20","21","22","23","24","25","26","27","28","29","30"], + RouterPermissions = ["1", "2", "3", "4","5","6","7","8","9","10","11","12","13","14"] }); } - - + var apiRouterPermissions = ControllerScanner.GetApiPermissions(); foreach (var permission in apiRouterPermissions) @@ -133,14 +133,23 @@ public class ApplicationDbContext(DbContextOptions options //创建列表 {name:"主页",rote:"/Home"} List rotePermissions = [ - new RotePermission { Id = 1, Name = "主页", Router = "/Home" }, - new RotePermission { Id = 2, Name = "用户", Router = "/User" }, - new RotePermission { Id = 3, Name = "cpu", Router = "/Host/Cpu" }, - new RotePermission { Id = 4, Name = "内存", Router = "/Host/Memory" } + new RotePermission { Id = 1, Name = "主页", Router = "/home" }, + new RotePermission { Id = 2, Name = "用户", Router = "^/user+$" }, + new RotePermission { Id = 3, Name = "cpu", Router = "/host/cpu" }, + new RotePermission { Id = 4, Name = "内存", Router = "/host/memory" }, + new RotePermission { Id = 5, Name = "磁盘", Router = "^/host/disk/.+$" }, + new RotePermission { Id = 6, Name = "网络设备", Router = "^/host/network/.+$" }, + new RotePermission { Id = 7, Name = "Gpu", Router = "^/host/gpu/.+$" }, + new RotePermission { Id = 8, Name = "用户详细", Router = "^/userinfo/.+$" }, + new RotePermission { Id = 9, Name = "用户监测", Router = "^/serveruser/.+$" }, + new RotePermission { Id = 10, Name = "进程列表", Router = "/host/process" }, + new RotePermission { Id = 11, Name = "网络连接列表", Router = "/host/networklist" }, + new RotePermission { Id = 12, Name = "巡检记录", Router = "/inspectionrecords" }, ]; foreach (var permission in rotePermissions) modelBuilder.Entity().HasData(permission); } + } public class ApplicationUser : IdentityUser @@ -151,7 +160,6 @@ public class ApplicationUser : IdentityUser [MaxLength(255)] public string? Desc { get; set; } - //职位 [MaxLength(255)] public required string Posts { get; set; } [MaxLength(255)] public string? Address { get; set; } diff --git a/LoongPanel-Asp/Configs/Alerts/4503e3c7-94c6-4ea7-acfc-d503031504fa.ini b/LoongPanel-Asp/Configs/Alerts/4503e3c7-94c6-4ea7-acfc-d503031504fa.ini new file mode 100644 index 0000000..6a7ffec --- /dev/null +++ b/LoongPanel-Asp/Configs/Alerts/4503e3c7-94c6-4ea7-acfc-d503031504fa.ini @@ -0,0 +1,3 @@ +[CpuTotalUsage_d3YT] +Notify=20 +Warning=30 \ No newline at end of file diff --git a/LoongPanel-Asp/Configs/alert.ini b/LoongPanel-Asp/Configs/alert.ini deleted file mode 100644 index 013ac4e..0000000 --- a/LoongPanel-Asp/Configs/alert.ini +++ /dev/null @@ -1,2 +0,0 @@ -[CpuTotalUsage] -Value=80 \ No newline at end of file diff --git a/LoongPanel-Asp/Configs/jobs.ini b/LoongPanel-Asp/Configs/jobs.ini index 8979c78..06ba629 100755 --- a/LoongPanel-Asp/Configs/jobs.ini +++ b/LoongPanel-Asp/Configs/jobs.ini @@ -68,4 +68,12 @@ ValueName = 网络总使用率 Description = A simple job that uses the Network JobType = LoongPanel_Asp.Jobs.NetworkTotalJob, LoongPanel-Asp Executor = d3YT,xseg -CronExpression = 3/15 * * * * ? * \ No newline at end of file +CronExpression = 3/15 * * * * ? * + +[UserTotalJob] +Group = User +ValueName = 分用户总使用 +Description = A simple job that uses the Network +JobType = LoongPanel_Asp.Jobs.UserTotalJob, LoongPanel-Asp +Executor = d3YT,xseg +CronExpression = 5/15 * * * * ? * \ No newline at end of file diff --git a/LoongPanel-Asp/Configs/servers.ini b/LoongPanel-Asp/Configs/servers.ini index bf3fd32..6264983 100644 --- a/LoongPanel-Asp/Configs/servers.ini +++ b/LoongPanel-Asp/Configs/servers.ini @@ -2,14 +2,14 @@ address = 192.168.0.26 port = 22 serverName = 龙芯 -password = loongnix -username = loongnix +password = loongpanel +username = loongpanel https = false [xseg] -address = 127.0.0.1 +address = 129.204.245.145 port = 22 -serverName = 本机 -password = 123123 -username = zwb +serverName = 远端debian +password = loongpanel +username = loongpanel https = false \ No newline at end of file diff --git a/LoongPanel-Asp/Controllers/AccountController.cs b/LoongPanel-Asp/Controllers/AccountController.cs index a38f2fc..73d5dda 100755 --- a/LoongPanel-Asp/Controllers/AccountController.cs +++ b/LoongPanel-Asp/Controllers/AccountController.cs @@ -18,141 +18,43 @@ public class AccountController( ILiteDatabase db) : ControllerBase { - [HttpPost("SendVerificationCode")] - public async Task SendVerificationCode([FromBody] EmailModel model) - { - if (!ModelState.IsValid) return BadRequest(ModelState); - - try - { - var code = emailHelper.GenerateVerificationCode(); - Console.WriteLine(code); - await emailHelper.SendVerificationEmailAsync(model.Email, code); - - // 生成过期时间 5分钟 - var expireTime = DateTime.Now.AddMinutes(5); - var col = db.GetCollection("EmailCode"); - var userCode = new EmailCode - { - Email = model.Email, - Code = code, - ExpireTime = expireTime - }; - - col.EnsureIndex(x => x.Email, true); - - // 使用 Upsert 来插入或更新验证码记录 - col.DeleteMany(x => x.Email == model.Email); - col.Insert(userCode); - - // 注册成功,返回一个合适的响应 - return Ok("Registration successful. Please check your email for the verification code."); - } - catch (Exception ex) - { - // Log the exception - Console.WriteLine(ex.Message); - // 返回一个错误响应 - return StatusCode(StatusCodes.Status500InternalServerError, - $"An error occurred while processing your request.{ex.Message}"); - } - } - [HttpPost("Register")] public async Task Register([FromBody] RegisterModel model) { - if (!ModelState.IsValid) return BadRequest(new ApiResponse(ApiResponseState.Error, "Invalid request")); - + if (!ModelState.IsValid) return BadRequest("错误的请求"); try { - // 获取code,email - var col = db.GetCollection("EmailCode"); - // 使用email查询 - var userCode = col.FindOne(x => x.Email == model.Email); - // 判断结果 - if (userCode == null) return BadRequest(new ApiResponse(ApiResponseState.Error, "Code not found")); - - if (userCode.Code != model.Code) - return BadRequest(new ApiResponse(ApiResponseState.Error, "Code does not match")); - - // 判断是否过期 - if (userCode.ExpireTime < DateTime.Now) - return BadRequest(new ApiResponse(ApiResponseState.Error, "Code expired")); - - // 注册用户 - if (HttpContext.RequestServices.GetService(typeof(UserManager)) is not - UserManager manager) - return StatusCode(StatusCodes.Status500InternalServerError, - new ApiResponse(ApiResponseState.Error, "UserManager not found")); - - var user = new ApplicationUser + //判断用户名,邮箱是否唯一 + var user = await userManager.FindByNameAsync(model.UserName); + if (user != null) return BadRequest("用户名已存在"); + + //创建用户 + user = new ApplicationUser { - UserName = model.UserName, - Email = model.Email, - PhoneNumber = model.Phone, - NickName = model.NickName, - EmailConfirmed = true, - Avatar = $"https://api.multiavatar.com/{model.UserName}.svg", - Posts = "员工", + Posts = model.Position, CreateDate = DateTime.UtcNow, - ModifiedDate = DateTime.UtcNow + ModifiedDate = DateTime.UtcNow, + Email = model.Email, + UserName = model.UserName, + PhoneNumber = model.Phone, + NickName = model.FullName, }; - - var result = await manager.CreateAsync(user, model.Password); - // 验证成功,删除验证码 - col.DeleteMany(x => x.Email == model.Email); - - if (!result.Succeeded) - return BadRequest(new ApiResponse(ApiResponseState.Error, "User creation failed", null, result.Errors)); - - // 添加用户角色 - if (HttpContext.RequestServices.GetService(typeof(RoleManager)) is not - RoleManager roleManager) - { - // 如果角色管理器不存在,删除刚刚创建的用户 - await manager.DeleteAsync(user); - return StatusCode(StatusCodes.Status500InternalServerError, - new ApiResponse(ApiResponseState.Error, "RoleManager not found")); - } - - var roleResult = await manager.AddToRoleAsync(user, "user"); - - if (!roleResult.Succeeded) - { - // 如果角色分配失败,删除刚刚创建的用户 - await manager.DeleteAsync(user); - return BadRequest(new ApiResponse(ApiResponseState.Error, "Role assignment failed", null, - roleResult.Errors)); - } - - // 用户注册成功 - return Ok(new ApiResponse(ApiResponseState.Success, "User registered successfully")); + var result = await userManager.CreateAsync(user, model.Password); + if (!result.Succeeded) return BadRequest("无法创建用户,"+string.Join(",",result.Errors.ToList().Select(e=>e.Description))); + + //添加用户到默认角色 + result = await userManager.AddToRoleAsync(user, model.Role); + if (!result.Succeeded) return BadRequest("无法创建用户,"+string.Join(",",result.Errors.ToList().Select(e=>e.Description))); + return Ok("用户创建成功"); } - catch (Exception ex) + catch (Exception e) { - // Log the exception - Console.WriteLine(ex.Message); - return StatusCode(StatusCodes.Status500InternalServerError, - "An error occurred while processing your request."); + Console.WriteLine(e); + throw; } } - - [HttpPost("VerifyEmailName")] - public async Task VerifyEmailName([FromBody] VerifyEmailNameModel model) - { - if (!ModelState.IsValid) return BadRequest(new ApiResponse(ApiResponseState.Error, "Invalid request", null)); - - var user = await userManager.FindByEmailAsync(model.Email); - - if (user != null) return Ok(new ApiResponse(ApiResponseState.Error, "Email already exists", null)); - - var userName = await userManager.FindByNameAsync(model.UserName); - - if (userName != null) return Ok(new ApiResponse(ApiResponseState.Error, "UserName already exists", null)); - - return Ok(new ApiResponse(ApiResponseState.Success, "Email and UserName are available", null)); - } + [HttpPost("Login")] public async Task Login([FromBody] LoginModel model) diff --git a/LoongPanel-Asp/Controllers/PublicFileController.cs b/LoongPanel-Asp/Controllers/PublicFileController.cs new file mode 100644 index 0000000..0a862fe --- /dev/null +++ b/LoongPanel-Asp/Controllers/PublicFileController.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Net.Http.Headers; + +namespace LoongPanel_Asp.Controllers; + +[ApiController] +[Route("Api/[controller]")] +public class PublicFileController(IWebHostEnvironment webHostEnvironment) : ControllerBase +{ + [HttpPost("UploadImage")] + public async Task UploadImage(IFormFile? file) + { + if (file == null || file.Length == 0) + { + return BadRequest("文件不能为空"); + } + var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif" }; + var extension = Path.GetExtension(file.FileName).ToLowerInvariant(); + Console.WriteLine(extension); + if (!allowedExtensions.Contains(extension)) + { + return BadRequest("不支持的文件类型"); + } + var uploadsFolderPath = Path.Combine(webHostEnvironment.WebRootPath, "public/image"); + if (!Directory.Exists(uploadsFolderPath)) + { + Directory.CreateDirectory(uploadsFolderPath); + } + + var uniqueFileName = $"{Guid.NewGuid()}{extension}"; + var filePath = Path.Combine(uploadsFolderPath, uniqueFileName); + await using var stream = new FileStream(filePath, FileMode.Create); + await file.CopyToAsync(stream); + var fileUrl = $"/public/image/{uniqueFileName}"; + return Ok(new { fileUrl }); + } +} \ No newline at end of file diff --git a/LoongPanel-Asp/Controllers/ServerController.cs b/LoongPanel-Asp/Controllers/ServerController.cs index 2e33789..f8568bd 100755 --- a/LoongPanel-Asp/Controllers/ServerController.cs +++ b/LoongPanel-Asp/Controllers/ServerController.cs @@ -1,5 +1,7 @@ using System.Collections.Concurrent; using System.Collections.Specialized; +using System.Dynamic; +using System.Globalization; using System.Net; using System.Net.Sockets; using System.Text; @@ -7,6 +9,7 @@ using System.Text.RegularExpressions; using LoongPanel_Asp.Helpers; using LoongPanel_Asp.Models; using LoongPanel_Asp.Servers; +using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -187,8 +190,8 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon var serverConfigs = JobConfigHelper.GetServers().ToList(); var server = serverConfigs.Find(x =>x.Id == serverId); if (server == null) return BadRequest(); - var output = await sshClient?.ExecuteCommandAsync(serverId,$"echo {server.Password}","|","sudo -S fdisk -l","|","grep 'Disk /'","|","awk '{print $2,$3}'")!; - if (string.IsNullOrEmpty(output)) return BadRequest(); + var output = await sshClient?.ExecuteCommandAsync(serverId,$"echo {server.Password}","|","sudo -S /usr/sbin/fdisk -l","|","grep 'Disk /'","|","awk '{print $2,$3}'")!; + if (string.IsNullOrEmpty(output)) return BadRequest(output); var diskList = output.Split("\n", StringSplitOptions.RemoveEmptyEntries); var outList = diskList.Select(disk => disk.Split(":", StringSplitOptions.RemoveEmptyEntries)).Select(info => new { name = info[0], size = info[1] }).ToList(); return Ok(outList); @@ -206,6 +209,19 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon var data = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList(); return Ok(data); } + [HttpGet("GetServerNetworkEquipmentInfo")] + public async Task GetServerNetworkEquipmentInfo([FromQuery] string serverId,[FromQuery] string networkId) + { + var sshClient = serviceProvider.GetService(); + var serverConfigs = JobConfigHelper.GetServers().ToList(); + var server = serverConfigs.Find(x =>x.Id == serverId); + if (server == null) return BadRequest(); + var output = await sshClient?.ExecuteCommandAsync(serverId,"/usr/sbin/ifconfig",networkId)!; + if (string.IsNullOrEmpty(output)) return BadRequest(); + output = string.Join(" ", output.Split("\n", StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim())); + var data = output.Split(" ", StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).Skip(1).ToList(); + return Ok(data); + } [HttpGet("GetServerGpuList")] public async Task GetServerGpuList([FromQuery] string serverId) { @@ -226,9 +242,19 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon var serverConfigs = JobConfigHelper.GetServers().ToList(); var server = serverConfigs.Find(x =>x.Id == serverId); if (server == null) return BadRequest(); + var type = "ata"; + if (diskId.StartsWith("nvme")) type = "nvme"; + else if (diskId.StartsWith("vd")) type = "ata"; + else if (diskId.StartsWith("sd")) type = "ata"; + else if (diskId.StartsWith("hd")) type = "ata"; diskId = $"/dev/{diskId}"; - var output = await sshClient?.ExecuteCommandAsync(serverId,$"echo '{server.Password}'","|","sudo -S","smartctl -i",diskId,"-T permissive","|","awk 'NR>4'")!; - if (string.IsNullOrEmpty(output)) return BadRequest(); + var output = await sshClient?.ExecuteCommandAsync(serverId,$"echo '{server.Password}'","|","sudo -S","/usr/sbin/smartctl -i",diskId,$"-d {type}","-T permissive","2>/dev/null","|","awk 'NR>4'")!; + if (string.IsNullOrEmpty(output)) return BadRequest(output); + if (output.Contains("=== START OF INFORMATION SECTION ===")) + { + //截断 + output = output.Substring(output.IndexOf("=== START OF INFORMATION SECTION ===", StringComparison.Ordinal) + "=== START OF INFORMATION SECTION ===".Length); + } var diskInfo = output .Split('\n', StringSplitOptions.RemoveEmptyEntries) .Select(line => line.Trim().Split(':')) @@ -240,34 +266,238 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon return Ok(diskInfo); } - [HttpGet("GetServerTerminalPath")] - public async Task GetServerTerminalPath([FromQuery] string serverId) + [HttpGet("GetServerUserList")] + public async Task GetServerUserList([FromQuery] string serverId) { - var serverConfig = JobConfigHelper.GetServers().Find(x => x.Id == serverId); - if (serverConfig == null) return BadRequest(); - //获得本机ip - string? localIp; - using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0)) + var sshClient = serviceProvider.GetService(); + var serverConfigs = JobConfigHelper.GetServers().ToList(); + var server = serverConfigs.Find(x =>x.Id == serverId); + if (server == null) return BadRequest(); + var output = await sshClient?.ExecuteCommandAsync(serverId,"awk -F: '$1 != \"nobody\" && $1 != \"build\" && $3 >= 1000 {print $1}'","/etc/passwd")!; + if (string.IsNullOrEmpty(output)) return BadRequest("无法获得用户树"); + var data = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList(); + output = await sshClient?.ExecuteCommandAsync(serverId,"w -husf","|"," awk '$2 !~ /^tty/ {print$1, $2}'"," |"," sort ","|"," uniq")!; + var onlineList = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList(); + output = await sshClient?.ExecuteCommandAsync(serverId,"lastlog | awk 'NR > 1 { if ($2 ~ /^**Never/) {print $1, \"-\",\"NULL\"} else {print $1,$2, substr($0,index($0,$3))}}' ")!; + if (string.IsNullOrEmpty(output)) return BadRequest("无法获得用户登录记录"); + var loginList = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList(); + var serverUserList = new List(); + data.ForEach(x => { - await socket.ConnectAsync("8.8.8.8", 65530); - var endPoint = socket.LocalEndPoint as IPEndPoint; - localIp = endPoint?.Address.ToString(); + var d = new ServerUserInfo + { + Name = x, + IsOnline = false, + LastLoginTime = null, + Port = null, + Address = "::1" + }; + serverUserList.Add(d); + }); + + onlineList.ForEach(x => + { + var line = x.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList(); + var d = serverUserList.Find(y => y.Name == line[0]); + if (d == null) return; + d.IsOnline = true; + d.Address = line[1]; + }); + + loginList.ForEach(x => + { + var len = x.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList(); + var name = len[0]; + var d = serverUserList.Find(y => y.Name == name); + if(d==null) return; + var port = len[1]; + var time = string.Join(" ",len.Skip(2).ToList()); + if (time != "NULL") + { + DateTimeOffset.TryParseExact(time, + "ddd MMM d HH:mm:ss zzz yyyy", + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var dateTimeOffset); + time = dateTimeOffset.ToString("G"); + } + d.LastLoginTime = time; + d.Port = port; + }); + serverUserList = serverUserList.OrderByDescending(x => x.IsOnline).ThenByDescending(x => x.LastLoginTime).ToList(); + return Ok(serverUserList); + } + + + [HttpGet("GetServerProcessesList")] + public async Task GetServerProcessesList([FromQuery] string serverId,[FromQuery] string? userName) + { + var sshClient = serviceProvider.GetService(); + var serverConfigs = JobConfigHelper.GetServers().ToList(); + var server = serverConfigs.Find(x => x.Id == serverId); + if (server == null) return BadRequest(); + var output = await sshClient?.ExecuteCommandAsync(serverId, + "ps -eo pid,user,%cpu,%mem,comm --sort=-%cpu | awk 'NR>1'",userName!=null?$"| grep {userName}":"" )!; + var data = output.Split("\n", StringSplitOptions.RemoveEmptyEntries); + var processList = data.Select(x => + { + x=x.Trim(); + var line = x.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList(); + return new + { + Pid=line[0], + User = line[1], + Cpu=line[2], + Memory=line[3], + ProcessName=string.Join(" ",line.Skip(4)) + }; + }).ToList(); + return Ok(processList); + } + + [HttpGet("GetServerProcessesKill")] + public async Task GetServerProcessesKill([FromQuery] string serverId, [FromQuery] string pid,[FromQuery] bool force=false) + { + var sshClient = serviceProvider.GetService(); + var serverConfigs = JobConfigHelper.GetServers().ToList(); + var server = serverConfigs.Find(x => x.Id == serverId); + if (server == null) return BadRequest(); + var output = await sshClient?.ExecuteCommandAsync(serverId, + $"echo {server.Password}","|","sudo -SS","kill",force?"-9":"-15",pid )!; + return Ok($"关闭信号已发送,{output}"); + } + [HttpGet("GetServerNetworkList")] + public async Task GetServerNetworkList([FromQuery] string serverId, [FromQuery] string? userName) + { + var sshClient = serviceProvider.GetService(); + var serverConfigs = JobConfigHelper.GetServers().ToList(); + var server = serverConfigs.Find(x => x.Id == serverId); + if (server == null) return BadRequest(); + var output = await sshClient?.ExecuteCommandAsync(serverId, + $"echo {server.Password}","|","sudo -S ss -tunapo")!; + if (string.IsNullOrEmpty(output)) return BadRequest("返回为空"); + try + { + var data = output.Split("\n", StringSplitOptions.RemoveEmptyEntries) + .Skip(1) // 跳过第一行 + .Select(x => x.Trim()) + .Select(x => x.Split(' ', StringSplitOptions.RemoveEmptyEntries)) + .Where(x => x.Length != 7).ToList(); + + var networkList = data.Select(x => new + { + netId=x[0], + recvQ=x[2], + sendQ=x[3], + addressForm=x[4], + addressTo=x[5], + process=x[6].Split("),(",StringSplitOptions.RemoveEmptyEntries).Select(s=>new + { + name=s.Split(",")[0].Replace("users:((\"","").Replace("\"",""), + pid=s.Split(",")[1].Split("=",StringSplitOptions.RemoveEmptyEntries)[1] + }) + }); + return Ok(networkList); } - //拼接网络路径 - var uriBuilder = new UriBuilder + catch (Exception e) { - Scheme = "http", - Host = localIp, - Port = 8888, - }; - var query = System.Web.HttpUtility.ParseQueryString(uriBuilder.Query); - query["hostname"]=serverConfig.Address; - query["username"] = serverConfig.Username; - query["password"] = Convert.ToBase64String(Encoding.UTF8.GetBytes(serverConfig.Password)); - query["port"] = serverConfig.Port.ToString(); - uriBuilder.Query = query.ToString(); - var url = uriBuilder.Uri.ToString(); - return Ok(url); + return BadRequest(e.Message); + } + } + + [HttpPost("UpLoadWord")] + public async Task UpLoadWord( + [FromQuery] string serverId, + [FromQuery] string userName, + [FromQuery] string wordId, + [FromBody] WordModel content) + { + // 拼接路径 markdowns/ServerId/id.json + var path = Path.Combine(AppContext.BaseDirectory,"markdowns",serverId, $"{wordId}.json"); + var createAt=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + var lastModifyAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + var directoryPath = Path.GetDirectoryName(path); + + if (!Directory.Exists(directoryPath)) + { + // 不存在则创建目录 + if (directoryPath != null) Directory.CreateDirectory(directoryPath); + } + + // 检查文件是否存在 + if (System.IO.File.Exists(path)) + { + // 读取现有JSON文件 + var json = await System.IO.File.ReadAllTextAsync(path); + // 反序列化JSON为动态对象 + dynamic existingWord = Newtonsoft.Json.JsonConvert.DeserializeObject(json) ?? new ExpandoObject();; + + // 使用内部的createAt更新createAt + createAt = existingWord.createAt; + } + + + // 创建新的配置对象 + var newWord = new WordFileModel + { + UserName = userName, + WordId = wordId, + Content = content.Content, + WordName = content.Name, + CreateAt = createAt, + LastModifyAt = lastModifyAt + }; + // 将新的配置对象序列化为JSON + var newJson = Newtonsoft.Json.JsonConvert.SerializeObject(newWord); + + // 覆盖写入新的JSON配置 + await System.IO.File.WriteAllTextAsync(path, newJson); + + // 返回成功响应 + return Ok("文件已经保存"); + } + + [HttpGet("GetWordList")] + public async Task GetWordList([FromQuery] string serverId) + { + var path = Path.Combine(AppContext.BaseDirectory, "markdowns", serverId); + var files = Directory.GetFiles(path); + var wordList = new List(); + foreach (var file in files) + { + var fileInfo = new FileInfo(file); + var json = await System.IO.File.ReadAllTextAsync(file); + var word = Newtonsoft.Json.JsonConvert.DeserializeObject(json); + //去除content + if (word == null) continue; + word.Content = null; + var fileSize = fileInfo.Length; + word.FileSize = fileSize.ToString(); + wordList.Add(word); + } + return Ok(wordList); + } + + [HttpGet("GetWordContent")] + public async Task GetWordContent([FromQuery] string serverId, [FromQuery] string wordId) + { + var path = Path.Combine(AppContext.BaseDirectory, "markdowns", serverId, wordId + ".json"); + var json = await System.IO.File.ReadAllTextAsync(path); + var word = Newtonsoft.Json.JsonConvert.DeserializeObject(json); + return Ok(word); + + } + [HttpGet("GetWordTemplates")] + public async Task GetWordTemplates() + { + var path = Path.Combine(AppContext.BaseDirectory, "markdowns", "templates"); + var files = Directory.GetFiles(path); + var templates = files.Select( x => new + { + name = Path.GetFileName(x).Replace(".md",""), + content= System.IO.File.ReadAllTextAsync(x).Result + }).ToList(); + return Ok(templates); } } @@ -276,3 +506,29 @@ public class ServerInfo public required string Name { get; init; } public required string Id { get; init; } } + +public class WordModel +{ + public required string Name { get; set; } + public required string Content { get; set; } +} + +public class WordFileModel +{ + public required string UserName { get; set; } + public required string WordId { get; set; } + public required string? Content { get; set; } + public string? FileSize { get; set; } + public required string CreateAt { get; set; } + public required string WordName { get; set;} + public required string LastModifyAt { get; set; } +} + +public class ServerUserInfo +{ + public required string Name { get; set; } + public required bool IsOnline { get; set; } + public required string? LastLoginTime { get; set; } + public required string? Address { get; set; } + public required string? Port { get; set; } +} diff --git a/LoongPanel-Asp/Helpers/DataHelper.cs b/LoongPanel-Asp/Helpers/DataHelper.cs index 2297d2a..0ad7783 100755 --- a/LoongPanel-Asp/Helpers/DataHelper.cs +++ b/LoongPanel-Asp/Helpers/DataHelper.cs @@ -1,6 +1,9 @@ -namespace LoongPanel_Asp.Helpers; +using LoongPanel_Asp.Hubs; +using Microsoft.AspNetCore.SignalR; -public class DataHelper(ApplicationDbContext dbContext) +namespace LoongPanel_Asp.Helpers; + +public class DataHelper(ApplicationDbContext dbContext,IHubContext context) { public async Task SaveData(ServerMonitoringData data) { @@ -27,11 +30,33 @@ public class DataHelper(ApplicationDbContext dbContext) await dbContext.SaveChangesAsync(); } - public static async Task CheckData(string serverId,string dataType,double value) + public async Task CheckData(string serverId,string valueType,string value,string valueName) { + var alertConfigs = JobConfigHelper.GetAlerts(); - var alert=alertConfigs[serverId][dataType]; - - + if (!alertConfigs.TryGetValue(serverId, out var serverAlert)) return; + serverAlert.Notify.TryGetValue(valueType, out var notifyValuePairs); + serverAlert.Warning.TryGetValue(valueType, out var warningValuePairs); + var matchingValues = warningValuePairs?.Where(pair => double.Parse(value) >= double.Parse(pair.Key)) + .SelectMany(pair => pair.Value).Distinct().ToList(); + + if (matchingValues?.Count > 0) + { + foreach (var item in matchingValues) + { + await context.Clients.Group(item).SendAsync("ReceiveWaring", value, valueName); + } + return; + } + + matchingValues = notifyValuePairs?.Where(pair => double.Parse(value) >= double.Parse(pair.Key)) + .SelectMany(pair => pair.Value).Distinct().ToList(); + if (matchingValues?.Count > 0) + { + foreach (var item in matchingValues) + { + await context.Clients.Group(item).SendAsync("ReceiveNotify", value, valueName); + } + } } } \ No newline at end of file diff --git a/LoongPanel-Asp/Helpers/JobConfigHelper.cs b/LoongPanel-Asp/Helpers/JobConfigHelper.cs index c195ed5..878c4ae 100755 --- a/LoongPanel-Asp/Helpers/JobConfigHelper.cs +++ b/LoongPanel-Asp/Helpers/JobConfigHelper.cs @@ -7,7 +7,7 @@ namespace LoongPanel_Asp.Helpers; public static class JobConfigHelper { private static List? _serverConfigs; - private static Dictionary>>? _alertsConfigs; + private static Dictionary? _alertsConfigs; public static IEnumerable ReadJobConfigurations() { @@ -71,53 +71,98 @@ public static class JobConfigHelper return _serverConfigs; } - public static Dictionary>> GetAlerts() + public static Dictionary GetAlerts() { if (_alertsConfigs != null) return _alertsConfigs; // 创建_alertsConfigs - _alertsConfigs = new Dictionary>>(); + var alertsConfigs = new Dictionary(); var parser = new FileIniDataParser(); var alertsFolderPath = Path.Combine(Environment.CurrentDirectory, "Configs", "Alerts"); - var defaultAlert = Path.Combine(Environment.CurrentDirectory, "Configs", "alert.ini"); // 检查目录是否存在,如果不存在则创建 if (!Directory.Exists(alertsFolderPath)) Directory.CreateDirectory(alertsFolderPath); - + // 获取目录下所有.ini文件的路径 var alertFiles = Directory.GetFiles(alertsFolderPath, "*.ini"); - foreach (var filePath in alertFiles) + var notifyUsersMap = new Dictionary>>>(); + var warningUsersMap = new Dictionary>>>(); + // 读取配置信息 + foreach (var alertFile in alertFiles) { - var name = Path.GetFileNameWithoutExtension(filePath); - var parts = name.Split('_'); - var serverId = parts[0] ; - var userId = parts[1]; - // 读取ini文件 - - var data = parser.ReadFile(filePath, Encoding.UTF8); - Console.WriteLine(data.ToString()); - // 获取所有section - foreach (var section in data.Sections) + var alertData = parser.ReadFile(alertFile, Encoding.UTF8); + var alertSections = alertData.Sections; + if (alertSections.Count == 0) continue; + //遍历每一个section + foreach (var alertSection in alertSections) { - // 解析每个section为AlertsModel - var type = section.SectionName; - var value = double.Parse(section.Keys["Value"]); - - // 添加到字典 - if (serverId != null && !(_alertsConfigs.ContainsKey(serverId))) + var serverId = alertSection.SectionName.Split("_")[1]; + var type = alertSection.SectionName.Split("_")[0]; + var notifyValue= alertSection.Keys["Notify"]; + var warningValue = alertSection.Keys["Warning"]; + var userId = Path.GetFileNameWithoutExtension(alertFile); + if (!notifyUsersMap.TryGetValue(serverId, out var notifyTypes)) { - _alertsConfigs[serverId] = new Dictionary>(); - } - if (serverId != null && !_alertsConfigs[serverId].ContainsKey(type)) - { - _alertsConfigs[serverId][type] = new Dictionary(); + notifyTypes = []; + notifyUsersMap.Add(serverId, notifyTypes); } - if (userId == null) continue; - if (serverId != null) _alertsConfigs[serverId][type][userId] = value; + if (!warningUsersMap.TryGetValue(serverId, out var warningTypes)) + { + warningTypes = []; + warningUsersMap.Add(serverId, warningTypes); + } + if (!notifyTypes.TryGetValue(type, out var notifyValues)) + { + notifyValues = []; + notifyTypes.Add(type, notifyValues); + } + + if (!warningTypes.TryGetValue(type, out var warningValues)) + { + warningValues = []; + warningTypes.Add(type, warningValues); + } + + if (!notifyValues.TryGetValue(notifyValue, out var notifyUsersList)) + { + notifyUsersList = []; + notifyValues.Add(notifyValue, notifyUsersList); + } + if (!warningValues.TryGetValue(warningValue, out var warningUsersList)) + { + warningUsersList = []; + warningValues.Add(warningValue, warningUsersList); + } + notifyUsersList.Add(userId); + warningUsersList.Add(userId); } } + //遍历 notifyUsersMap + foreach (var (serverId, notifyUsersList) in notifyUsersMap) + { + //获得key 和 value + if(!alertsConfigs.TryGetValue(serverId, out var alertsConfig)){ + //创建新的 + alertsConfig = new AlertConfiguration(); + alertsConfigs.Add(serverId, alertsConfig); + } + alertsConfig.Notify= notifyUsersList; + } - return _alertsConfigs; + foreach (var (serverId, emailUsersList) in warningUsersMap) + { + //获得key 和 value + if (!alertsConfigs.TryGetValue(serverId, out var alertsConfig)) + { + //创建新的 + alertsConfig = new AlertConfiguration(); + alertsConfigs.Add(serverId, alertsConfig); + } + alertsConfig.Warning = emailUsersList; + } + + _alertsConfigs =alertsConfigs; + return alertsConfigs; } } @@ -144,3 +189,10 @@ public class JobConfiguration //ValueName public string? ValueName { get; init; } } + +public class AlertConfiguration(Dictionary>>? notify=null, + Dictionary>>? warning=null) +{ + public Dictionary>> Notify { get; set; } = notify ?? []; + public Dictionary>> Warning { get; set; } = warning ?? []; +} diff --git a/LoongPanel-Asp/Hubs/SessionHub.cs b/LoongPanel-Asp/Hubs/SessionHub.cs index 68b4760..56fd3d0 100755 --- a/LoongPanel-Asp/Hubs/SessionHub.cs +++ b/LoongPanel-Asp/Hubs/SessionHub.cs @@ -11,11 +11,8 @@ public class SessionHub(UserManager userManager, ILiteDatabase public override async Task OnConnectedAsync() { var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value; - Console.WriteLine(userId); //获得登陆ip var ip = Context.GetHttpContext()!.Connection.RemoteIpAddress; - Console.WriteLine(ip); - Console.WriteLine(userId); await Groups.AddToGroupAsync(Context.ConnectionId, userId); // 查询获取用户对象 var user = await userManager.FindByIdAsync(userId); diff --git a/LoongPanel-Asp/Hubs/TerminalHub.cs b/LoongPanel-Asp/Hubs/TerminalHub.cs new file mode 100644 index 0000000..6391a7e --- /dev/null +++ b/LoongPanel-Asp/Hubs/TerminalHub.cs @@ -0,0 +1,36 @@ +using System.Security.Claims; +using LoongPanel_Asp.Servers; +using Microsoft.AspNetCore.SignalR; + +namespace LoongPanel_Asp.Hubs; + +public class TerminalHub(SshStreamService sshStreamService):Hub +{ + + public override async Task OnConnectedAsync() + { + var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value; + await Groups.AddToGroupAsync(Context.ConnectionId, userId); + } + //create a terminal + public Task CreateTerminal(string serverId) + { + var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value; + sshStreamService.Connect(userId, serverId); + return Task.CompletedTask; + } + //send a message to the terminal + public async Task SendMessage(string message) + { + var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value; + Console.WriteLine(message); + sshStreamService.Write(userId,message); + } + //断开 + public override Task OnDisconnectedAsync(Exception? exception) + { + var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value; + sshStreamService.Disconnect(userId); + return base.OnDisconnectedAsync(exception); + } +} \ No newline at end of file diff --git a/LoongPanel-Asp/Init.cs b/LoongPanel-Asp/Init.cs new file mode 100644 index 0000000..4127727 --- /dev/null +++ b/LoongPanel-Asp/Init.cs @@ -0,0 +1,62 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using System.Threading; +using System.Threading.Tasks; +using LoongPanel_Asp.Helpers; + +namespace LoongPanel_Asp +{ + public class Init : IHostedService + { + private readonly IServiceProvider _serviceProvider; + + public Init(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + using var scope = _serviceProvider.CreateScope(); + var userManager = scope.ServiceProvider.GetRequiredService>(); + var roleManager = scope.ServiceProvider.GetRequiredService>(); + // 检查管理员用户是否存在 + var adminUser = await userManager.FindByNameAsync("admin"); + if (adminUser == null) + { + adminUser = new ApplicationUser + { + Avatar = "https://api.multiavatar.com/admin.svg", + Posts = "管理员", + CreateDate = DateTime.UtcNow, + ModifiedDate = DateTime.UtcNow, + Email = "admin@admin.com", + UserName = "admin", + PhoneNumber = "999999999", + NickName = "默认管理员", + }; + + var result = await userManager.CreateAsync(adminUser, "Qwertyuiop123!@#"); + //分配管理员角色 + if (result.Succeeded) + await userManager.AddToRoleAsync(adminUser, "admin"); + if (result.Succeeded) + Console.WriteLine("管理员创建成功,账号:{0},密码:{1}", "admin", "Qwertyuiop123!@#"); + else + foreach (var error in result.Errors) + { + Console.WriteLine(error.Description); + } + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + // 这里可以执行一些应用程序停止时的清理操作 + return Task.CompletedTask; + } + } +} diff --git a/LoongPanel-Asp/Jobs/CpuJob.cs b/LoongPanel-Asp/Jobs/CpuJob.cs index 5ede169..53f3534 100755 --- a/LoongPanel-Asp/Jobs/CpuJob.cs +++ b/LoongPanel-Asp/Jobs/CpuJob.cs @@ -19,12 +19,12 @@ public class CpuTotalJob( public async Task Execute(IJobExecutionContext context) { var dataMap = context.JobDetail.JobDataMap; - var dataHelper = new DataHelper(dbContext); + var dataHelper = new DataHelper(dbContext,hubContext); var serverList = (List)dataMap["executor"]; var cpuDataListAll = new List(); + var sshClient = serviceProvider.GetService(); foreach (var server in serverList) { - var sshClient = serviceProvider.GetService(); var output = await sshClient?.ExecuteCommandAsync(server.Id, "sar", "-u", "3 1", "|", "grep", "Average")!; if (string.IsNullOrEmpty(output)) continue; @@ -50,9 +50,10 @@ public class CpuTotalJob( DataType = "CpuTotalUsage" }; cpuDataList.Add(totalUsage); - cpuDataList.ForEach(data => + cpuDataList.ForEach(async data => { - hubContext.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data); + await hubContext.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data); + await dataHelper.CheckData(server.Id, data.DataType??"", data.Data??"", data.DataName); }); cpuDataListAll.AddRange(cpuDataList); } @@ -75,12 +76,12 @@ public class CpuSpeedJob( public async Task Execute(IJobExecutionContext context) { var dataMap = context.JobDetail.JobDataMap; - var dataHelper = new DataHelper(dbContext); + var dataHelper = new DataHelper(dbContext,hubContext); var serverList = (List)dataMap["executor"]; var cpuDataListAll = new List(); + var sshClient = serviceProvider.GetService(); foreach (var server in serverList) { - var sshClient = serviceProvider.GetService(); var output = await sshClient?.ExecuteCommandAsync(server.Id, "cat", "/proc/cpuinfo", "|", "grep", "'cpu MHz'")!; if (string.IsNullOrEmpty(output)) continue; @@ -112,10 +113,10 @@ public class CpuSpeedJob( } cpuDataListAll.AddRange(cpuDataList); - cpuDataList.ForEach(data => + cpuDataList.ForEach(async data => { - hubContext.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data); - if (data is { Data: not null, DataType: not null }) _ = DataHelper.CheckData(server.Id, data.DataType, double.Parse(data.Data)); + await hubContext.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data); + await dataHelper.CheckData(server.Id, data.DataType ?? "", data.Data ?? "", data.DataName); }); } @@ -137,7 +138,7 @@ public class CpuSingleUsageJob( public async Task Execute(IJobExecutionContext context) { var dataMap = context.JobDetail.JobDataMap; - var dataHelper = new DataHelper(dbContext); + var dataHelper = new DataHelper(dbContext,hubContext); var serverList = (List)dataMap["executor"]; var cpuDataListAll = new List(); foreach (var server in serverList) @@ -161,9 +162,10 @@ public class CpuSingleUsageJob( cpuDataList.Add(singleUsage); } - cpuDataList.ForEach(data => + cpuDataList.ForEach(async data => { - hubContext.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data); + await hubContext.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data); + await dataHelper.CheckData(server.Id, data.DataType ?? "", data.Data ?? "", data.DataName); }); } diff --git a/LoongPanel-Asp/Jobs/DiskJob.cs b/LoongPanel-Asp/Jobs/DiskJob.cs index 38bba03..6d99585 100755 --- a/LoongPanel-Asp/Jobs/DiskJob.cs +++ b/LoongPanel-Asp/Jobs/DiskJob.cs @@ -17,7 +17,7 @@ public class DiskTotalJob( public async Task Execute(IJobExecutionContext context) { var dataMap = context.JobDetail.JobDataMap; - var dataHelper = new DataHelper(dbContext); + var dataHelper = new DataHelper(dbContext,hubContext); var serverList = (List)dataMap["executor"]; //获得cpu信息 var diskDataListAll = new List(); @@ -58,7 +58,7 @@ public class DiskUseJob( public async Task Execute(IJobExecutionContext context) { var dataMap = context.JobDetail.JobDataMap; - var dataHelper = new DataHelper(dbContext); + var dataHelper = new DataHelper(dbContext,hubContext); var serverList = (List)dataMap["executor"]; //获得cpu信息 var diskDataListAll = new List(); @@ -81,7 +81,7 @@ public class DiskUseJob( ServerId = server.Id, Data = disk[1], DataName = $"磁盘每秒传输数-{dev}" , - DataType = $"diskTps-{dev}" + DataType = $"DiskTps-{dev}" }; diskDataList.Add(diskTps); var diskReadKb = new ServerMonitoringData @@ -89,7 +89,7 @@ public class DiskUseJob( ServerId = server.Id, Data = disk[2], DataName = $"磁盘每秒读取数据量-{dev}" , - DataType = $"diskReadKB-{dev}" + DataType = $"DiskReadKB-{dev}" }; diskDataList.Add(diskReadKb); var diskWriteKb = new ServerMonitoringData @@ -97,7 +97,7 @@ public class DiskUseJob( ServerId = server.Id, Data = disk[3], DataName = $"磁盘每秒写入数据量-{dev}" , - DataType = $"diskWriteKB-{dev}" + DataType = $"DiskWriteKB-{dev}" }; diskDataList.Add(diskWriteKb); var diskAwait = new ServerMonitoringData @@ -105,7 +105,7 @@ public class DiskUseJob( ServerId = server.Id, Data = disk[7], DataName = $"磁盘平均等待时间-{dev}" , - DataType = $"diskAwait-{dev}" + DataType = $"DiskAwait-{dev}" }; diskDataList.Add(diskAwait); var diskUtil = new ServerMonitoringData @@ -113,7 +113,7 @@ public class DiskUseJob( ServerId = server.Id, Data = disk[8], DataName = $"磁盘利用率-{dev}" , - DataType = $"diskUtil-{dev}" + DataType = $"DiskUtil-{dev}" }; diskDataList.Add(diskUtil); diskDataList.ForEach(data => diff --git a/LoongPanel-Asp/Jobs/MemoryJob.cs b/LoongPanel-Asp/Jobs/MemoryJob.cs index 214bf8f..6d12fa7 100755 --- a/LoongPanel-Asp/Jobs/MemoryJob.cs +++ b/LoongPanel-Asp/Jobs/MemoryJob.cs @@ -18,7 +18,7 @@ public class MemoryTotalJob( public async Task Execute(IJobExecutionContext context) { var dataMap = context.JobDetail.JobDataMap; - var dataHelper = new DataHelper(dbContext); + var dataHelper = new DataHelper(dbContext,hubContext); // 从JobDataMap中获取参数 var serverList = (List)dataMap["executor"]; diff --git a/LoongPanel-Asp/Jobs/NetworkJob.cs b/LoongPanel-Asp/Jobs/NetworkJob.cs index 4e2c04a..65f5e11 100755 --- a/LoongPanel-Asp/Jobs/NetworkJob.cs +++ b/LoongPanel-Asp/Jobs/NetworkJob.cs @@ -18,7 +18,7 @@ public class NetworkTotalJob( public async Task Execute(IJobExecutionContext context) { var dataMap = context.JobDetail.JobDataMap; - var dataHelper = new DataHelper(dbContext); + var dataHelper = new DataHelper(dbContext,hubContext); var serverList = (List)dataMap["executor"]; var netWorkDataListAll = new List(); foreach (var server in serverList) diff --git a/LoongPanel-Asp/Jobs/ProcessJob.cs b/LoongPanel-Asp/Jobs/ProcessJob.cs index b70aefc..975a6dd 100644 --- a/LoongPanel-Asp/Jobs/ProcessJob.cs +++ b/LoongPanel-Asp/Jobs/ProcessJob.cs @@ -17,12 +17,12 @@ public class ProcessTotalJob( public async Task Execute(IJobExecutionContext context) { var dataMap = context.JobDetail.JobDataMap; - var dataHelper = new DataHelper(dbContext); + var dataHelper = new DataHelper(dbContext,hubContext); var serverList = (List)dataMap["executor"]; var processDataListAll = new List(); + var sshClient = serviceProvider.GetService(); foreach (var server in serverList) { - var sshClient = serviceProvider.GetService(); var output = await sshClient?.ExecuteCommandAsync(server.Id, "ps", "-e", "|", "wc", "-l")!; if (string.IsNullOrEmpty(output)) continue; var processDataList = new List(); @@ -70,12 +70,12 @@ public class PhrasePatternJob( public async Task Execute(IJobExecutionContext context) { var dataMap = context.JobDetail.JobDataMap; - var dataHelper = new DataHelper(dbContext); + var dataHelper = new DataHelper(dbContext,hubContext); var serverList = (List)dataMap["executor"]; var processDataListAll = new List(); + var sshClient = serviceProvider.GetService(); foreach (var server in serverList) { - var sshClient = serviceProvider.GetService(); var output = await sshClient?.ExecuteCommandAsync(server.Id, "lsof", "|", "wc", "-l")!; if (string.IsNullOrEmpty(output)) continue; var count = int.Parse(output); diff --git a/LoongPanel-Asp/Jobs/UserJob.cs b/LoongPanel-Asp/Jobs/UserJob.cs new file mode 100644 index 0000000..d461026 --- /dev/null +++ b/LoongPanel-Asp/Jobs/UserJob.cs @@ -0,0 +1,71 @@ +using LoongPanel_Asp.Helpers; +using LoongPanel_Asp.Hubs; +using LoongPanel_Asp.Models; +using LoongPanel_Asp.Servers; +using Microsoft.AspNetCore.SignalR; +using Quartz; + +namespace LoongPanel_Asp.Jobs; + +public class UserTotalJob(IHubContext hubContext, + IServiceProvider serviceProvider, + ApplicationDbContext dbContext): IJob +{ + private static int _count; + public async Task Execute(IJobExecutionContext context) + { + // 执行用户统计任务 + var dataMap = context.JobDetail.JobDataMap; + var dataHelper = new DataHelper(dbContext,hubContext); + var serverList = (List)dataMap["executor"]; + var userDataListAll = new List(); + var sshClient = serviceProvider.GetService(); + foreach (var server in serverList) + { + var output = await sshClient?.ExecuteCommandAsync(server.Id, "ps -o ruser=userForLongName -eo user,pcpu,pmem,comm --sort=-pcpu | awk 'NR>1 && $1 !~ /^systemd/ {user[$1]+=$2; mem[$1]+=$3; count[$1]++; total[$1]=$2+$3} END {for (u in user) print u, user[u], mem[u]/count[u], count[u]}' | sort -k1,1r -k2,2nr")!; + if (string.IsNullOrEmpty(output)) continue; + var lines = output.Split("\n",StringSplitOptions.RemoveEmptyEntries); + + foreach (var line in lines) + { + var d = line.Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList(); + var name = d[0]; + var cpu = d[1]; + var mem = d[2]; + var command=d[3]; + var data = new ServerMonitoringData + { + ServerId = server.Id, + Data = cpu, + DataName = $"CPU使用率-{name}", + DataType = $"CpuUsage-{name}" + }; + userDataListAll.Add(data); + data = new ServerMonitoringData + { + ServerId = server.Id, + Data = mem, + DataName = $"内存使用率-{name}", + DataType = $"MemoryUsage-{name}" + }; + userDataListAll.Add(data); + data = new ServerMonitoringData + { + ServerId = server.Id, + Data = command, + DataName = $"用户进程数-{name}", + DataType = $"UserProcesses-{name}" + }; + userDataListAll.Add(data); + } + } + userDataListAll.ForEach(data => + { + hubContext.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data); + }); + _count++; + if (_count <= 10) return; + _count = 0; + await dataHelper.SaveData(userDataListAll); + } +} \ No newline at end of file diff --git a/LoongPanel-Asp/LoongPanel-Asp.csproj b/LoongPanel-Asp/LoongPanel-Asp.csproj index bc2e4c0..8277ab7 100755 --- a/LoongPanel-Asp/LoongPanel-Asp.csproj +++ b/LoongPanel-Asp/LoongPanel-Asp.csproj @@ -18,6 +18,7 @@ + @@ -36,9 +37,12 @@ PreserveNewest + + PreserveNewest + - + diff --git a/LoongPanel-Asp/LoongPanel-Asp.csproj.user b/LoongPanel-Asp/LoongPanel-Asp.csproj.user index 9ff5820..45c6338 100755 --- a/LoongPanel-Asp/LoongPanel-Asp.csproj.user +++ b/LoongPanel-Asp/LoongPanel-Asp.csproj.user @@ -1,6 +1,10 @@  - https + http + C:\Users\niyyz\RiderProjects\LoongPanel-Asp\LoongPanel-Asp\Properties\PublishProfiles\registry.hub.docker.com_zwb.pubxml + + + ProjectDebugger \ No newline at end of file diff --git a/LoongPanel-Asp/Middlewares/ApiPermissionMiddleware.cs b/LoongPanel-Asp/Middlewares/ApiPermissionMiddleware.cs index b76c093..0e088dd 100755 --- a/LoongPanel-Asp/Middlewares/ApiPermissionMiddleware.cs +++ b/LoongPanel-Asp/Middlewares/ApiPermissionMiddleware.cs @@ -15,7 +15,7 @@ public class ApiPermissionMiddleware( { // 获取配置中定义的公开API列表 var publicApis = configuration["PublicApi"]?.Split(";", StringSplitOptions.RemoveEmptyEntries) ?? - new string[0]; + []; // 如果请求路径在公开API列表中,则直接调用下一个中间件 if (publicApis.Any(api => api == context.Request.Path.Value)) @@ -23,12 +23,17 @@ public class ApiPermissionMiddleware( await next(context); return; } - + + if (context.Request.Path.Value!.StartsWith("/public")) + { + await next(context); + return; + } // 验证Token var payload = context.User; - string[] hubKeywords = { "ServerHub", "MessageHub", "SessionHub", "TermHub" }; + string[] hubKeywords = { "ServerHub", "MessageHub", "SessionHub", "TerminalHub" }; //如果请求的地址是 (*Hub/*) if (hubKeywords.Any(keyword => context.Request.Path.Value!.Contains(keyword))) { diff --git a/LoongPanel-Asp/Middlewares/PermissionMiddleware.cs b/LoongPanel-Asp/Middlewares/PermissionMiddleware.cs index 2016908..6226f00 100755 --- a/LoongPanel-Asp/Middlewares/PermissionMiddleware.cs +++ b/LoongPanel-Asp/Middlewares/PermissionMiddleware.cs @@ -19,6 +19,12 @@ public class PermissionMiddleware( return; } Console.WriteLine(context.Request.Path.Value!); + //如果访问 /public/* + if (context.Request.Path.Value!.StartsWith("/public")) + { + await next(context); + return; + } // 获取请求头中的Authorization信息 var authorizationHeader = context.Request.Headers["Authorization"]; diff --git a/LoongPanel-Asp/Models/AccountModel.cs b/LoongPanel-Asp/Models/AccountModel.cs index 894f774..e4ee375 100755 --- a/LoongPanel-Asp/Models/AccountModel.cs +++ b/LoongPanel-Asp/Models/AccountModel.cs @@ -5,18 +5,15 @@ public class EmailModel public required string Email { get; set; } } -public class VerifyEmailNameModel : EmailModel -{ - public required string UserName { get; set; } -} public class RegisterModel : EmailModel { public required string UserName { get; set; } - public required string NickName { get; set; } - public required string Code { get; set; } + public required string FullName { get; set; } public required string Phone { get; set; } public required string Password { get; set; } + public required string Position { get; set; } + public required string Role { get; set; } } public class LoginModel diff --git a/LoongPanel-Asp/Program.cs b/LoongPanel-Asp/Program.cs index d96dc36..dab82a3 100755 --- a/LoongPanel-Asp/Program.cs +++ b/LoongPanel-Asp/Program.cs @@ -80,8 +80,8 @@ builder.Services.AddCors(options => policy => { //允许全部 - policy.WithOrigins("http://localhost:3001", "http://192.168.0.13:3001", "http://192.168.0.13:3002", - "http://192.168.0.13").AllowAnyHeader().AllowAnyMethod().AllowCredentials(); + policy.WithOrigins("http://localhost:3001", "http://192.168.0.13:3001", "https://192.168.0.13:3001", + "https://192.168.0.13:3000").AllowAnyHeader().AllowAnyMethod().AllowCredentials(); }); }); @@ -107,6 +107,13 @@ builder.Services.AddQuartzServer(options => }); builder.Services.AddScoped(); +builder.Services.AddSingleton(); + +builder.Services.AddHostedService(); + + + + var app = builder.Build(); @@ -119,11 +126,10 @@ if (app.Environment.IsDevelopment()) app.UseCors(myAllowSpecificOrigins); - +app.UseHttpsRedirection(); +app.UseStaticFiles(); app.UseMiddleware(); app.UseMiddleware(); - - app.UseHttpsRedirection(); app.UseAuthorization(); @@ -131,4 +137,5 @@ app.UseAuthorization(); app.MapControllers(); app.MapHub("/SessionHub"); +app.MapHub("/TerminalHub"); app.Run(); \ No newline at end of file diff --git a/LoongPanel-Asp/Servers/SSHService.cs b/LoongPanel-Asp/Servers/SSHService.cs index 2f0c4ca..dc5cf3b 100755 --- a/LoongPanel-Asp/Servers/SSHService.cs +++ b/LoongPanel-Asp/Servers/SSHService.cs @@ -54,8 +54,8 @@ public class SshService : IDisposable { // 确保在执行命令前连接到服务器 if (!sshClient.IsConnected) await sshClient.ConnectAsync(_cts.Token); - var commandString = string.Join(" ", "LANG=C", command, string.Join(" ", arguments)); + Console.WriteLine(commandString); using var commandResult = sshClient.RunCommand(commandString); output = commandResult.Result; if (commandResult.ExitStatus != 0) output = commandResult.Error; diff --git a/LoongPanel-Asp/Servers/SSHStreamService.cs b/LoongPanel-Asp/Servers/SSHStreamService.cs new file mode 100644 index 0000000..f856544 --- /dev/null +++ b/LoongPanel-Asp/Servers/SSHStreamService.cs @@ -0,0 +1,112 @@ +using Renci.SshNet; +using System; +using System.Collections.Concurrent; +using System.IO; +using LoongPanel_Asp.Helpers; +using LoongPanel_Asp.Hubs; +using Microsoft.AspNetCore.SignalR; +using ConnectionInfo = Renci.SshNet.ConnectionInfo; + +namespace LoongPanel_Asp.Servers; + +public class SshStreamService : IDisposable +{ + private readonly ConcurrentDictionary _sshStreams = new(); + private readonly IHubContext _hubContext; + private CancellationTokenSource _cancellationTokenSource; + private Task _readingTask; + + public SshStreamService(IHubContext hubContext) + { + _hubContext = hubContext; + _cancellationTokenSource = new CancellationTokenSource(); + _readingTask = Task.Run(() => StartReadingAsync(_cancellationTokenSource.Token)); + } + public void Connect(string userId, string serverId) + { + // 假设 JobConfigHelper.GetServers() 返回一个包含服务器配置的列表 + var serverConfig = JobConfigHelper.GetServers().Find(x => x.Id == serverId); + if (serverConfig == null) throw new InvalidOperationException("Server not found."); + //从_sshStreams查找是否存在 替换 + + var host = serverConfig.Address; + var port = serverConfig.Port; + var username = serverConfig.Username; + var password = serverConfig.Password; + + var connectionInfo = new ConnectionInfo(host, port, username, new PasswordAuthenticationMethod(username, password)); + var sshClient = new SshClient(connectionInfo); + sshClient.Connect(); + var shellStream = sshClient.CreateShellStream("xterm", 100, 40, 800, 600, 1024); + if (_sshStreams.TryGetValue(userId, out var existing)) + { + // 关闭旧的连接 + existing.Client.Disconnect(); + existing.Client.Dispose(); + existing.Stream.Close(); + existing.Stream.Dispose(); + } + + _sshStreams[userId]= (sshClient, shellStream); + } + + + public void Write(string userId, string data) + { + if (!_sshStreams.TryGetValue(userId, out var sshStreamInfo)) return; + var (_, shellStream) = sshStreamInfo; + shellStream?.Write(data); + } + + public void Disconnect(string userId) + { + if (_sshStreams.TryRemove(userId, out var sshStreamInfo)) + { + var (sshClient, shellStream) = sshStreamInfo; + shellStream?.Close(); + shellStream?.Dispose(); + sshClient.Disconnect(); + sshClient.Dispose(); + } + } + + public void Dispose() + { + _cancellationTokenSource.Cancel(); + try + { + _readingTask.Wait(); + } + catch (AggregateException ae) + { + ae.Handle(e => e is OperationCanceledException); + } + + foreach (var sshStreamInfo in _sshStreams.Values) + { + var (sshClient, shellStream) = sshStreamInfo; + shellStream?.Close(); + shellStream?.Dispose(); + sshClient.Disconnect(); + sshClient.Dispose(); + } + _sshStreams.Clear(); + } + + private async Task StartReadingAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + foreach (var (key, sshStreamInfo) in _sshStreams) + { + var (_, shellStream) = sshStreamInfo; + if (shellStream is not { CanRead: true, Length: > 0 }) continue; + var output = shellStream.Read(); + await _hubContext.Clients.Group(key).SendAsync("ReceiveMessage", output); + } + + // 等待一段时间再进行下一次检查 + await Task.Delay(10, cancellationToken); + } + } +} \ No newline at end of file diff --git a/LoongPanel-Asp/UserConfigs/Layouts/default.json b/LoongPanel-Asp/UserConfigs/Layouts/default.json index b02e958..5034510 100644 --- a/LoongPanel-Asp/UserConfigs/Layouts/default.json +++ b/LoongPanel-Asp/UserConfigs/Layouts/default.json @@ -1,58 +1,11 @@ { "md": [ - { - "type": "chart", - "chartRage": 6, - "serverValues": [ - { - "dataName": "CPU总使用率", - "dataType": "CpuTotalUsage" - }, - { - "dataName": "内存使用率", - "dataType": "MemoryUsage" - }, - { - "dataName": "磁盘总使用率", - "dataType": "DiskTotalUsage" - } - ], - "i": "069299c5-ceec-41cd-a1a8-b18ce0598317", - "x": 0, - "y": 0, - "h": 12, - "w": 10, - "selectChart": "line", - "cardConfig": { - "name": { - "label": "卡片名称", - "value": "系统信息卡片", - "type": "text" - }, - "description": { - "label": "卡片描述", - "value": "这是一张默认的卡片", - "type": "text" - }, - "foreground": { - "label": "卡片前景色", - "value": "ffffff", - "type": "color" - }, - "color": { - "label": "图表主色", - "value": "002EA6", - "type": "color" - } - }, - "moved": false - }, { "type": "card", "h": 16, - "w": 6, - "x": 4, - "y": 12, + "w": 3, + "x": 0, + "y": 15, "i": "68af2024-4a21-4351-b1ce-707aeeb3ea9c", "selectCard": "systemInfo", "moved": false @@ -60,9 +13,9 @@ { "type": "card", "h": 16, - "w": 4, - "x": 0, - "y": 12, + "w": 3, + "x": 3, + "y": 15, "i": "8104448e-6c47-455f-b635-43b3ac75d4df", "selectCard": "onlineUsers", "moved": false @@ -81,10 +34,10 @@ } ], "i": "6aa12623-fee2-48e3-86f1-88fcd538c4a8", - "x": 6, - "y": 44, + "x": 3, + "y": 47, "h": 16, - "w": 4, + "w": 3, "selectChart": "pie", "cardConfig": { "name": { @@ -129,9 +82,9 @@ ], "i": "17334b32-cede-441f-9bef-772d170f1c2a", "x": 0, - "y": 44, + "y": 47, "h": 16, - "w": 6, + "w": 3, "selectChart": "histogram", "cardConfig": { "name": { @@ -172,9 +125,9 @@ ], "i": "06e64544-dfb8-4a28-b777-96cb4fd70aec", "x": 0, - "y": 28, + "y": 31, "h": 16, - "w": 10, + "w": 9, "selectChart": "area", "cardConfig": { "name": { @@ -199,61 +152,61 @@ } }, "moved": false + }, + { + "type": "chart", + "chartRage": 6, + "serverValues": [ + { + "dataName": "CPU总使用率", + "dataType": "CpuTotalUsage" + }, + { + "dataName": "内存总使用率", + "dataType": "MemoryTotalUsage" + }, + { + "dataName": "网络接口总体使用率", + "dataType": "InterfaceTotalUtilizationPercentage" + } + ], + "i": "ae1f81ab-19dc-44ad-9120-fcdea694d5c4", + "x": 0, + "y": 0, + "h": 15, + "w": 6, + "selectChart": "line", + "cardConfig": { + "name": { + "label": "卡片名称", + "value": "默认卡片名称", + "type": "text" + }, + "description": { + "label": "卡片描述", + "value": "这是一张默认的卡片", + "type": "text" + }, + "foreground": { + "label": "卡片前景色", + "value": "ffffff", + "type": "color" + }, + "color": { + "label": "图表主色", + "value": "002EA6", + "type": "color" + } + }, + "moved": false } ], "lg": [ - { - "type": "chart", - "chartRage": 6, - "serverValues": [ - { - "dataName": "CPU总使用率", - "dataType": "CpuTotalUsage" - }, - { - "dataName": "内存使用率", - "dataType": "MemoryUsage" - }, - { - "dataName": "磁盘总使用率", - "dataType": "DiskTotalUsage" - } - ], - "i": "069299c5-ceec-41cd-a1a8-b18ce0598317", - "x": 0, - "y": 0, - "h": 13, - "w": 10, - "selectChart": "line", - "cardConfig": { - "name": { - "label": "卡片名称", - "value": "系统信息卡片", - "type": "text" - }, - "description": { - "label": "卡片描述", - "value": "这是一张默认的卡片", - "type": "text" - }, - "foreground": { - "label": "卡片前景色", - "value": "ffffff", - "type": "color" - }, - "color": { - "label": "图表主色", - "value": "002EA6", - "type": "color" - } - }, - "moved": false - }, { "type": "card", "h": 13, - "w": 2, - "x": 10, + "w": 3, + "x": 9, "y": 0, "i": "68af2024-4a21-4351-b1ce-707aeeb3ea9c", "selectCard": "systemInfo", @@ -321,8 +274,8 @@ "dataType": "DiskTotalUsage" }, { - "dataName": "内存使用率", - "dataType": "MemoryUsage" + "dataName": "内存总使用率", + "dataType": "MemoryTotalUsage" }, { "dataName": "CPU总使用率", @@ -401,9 +354,7 @@ } }, "moved": false - } - ], - "xl": [ + }, { "type": "chart", "chartRage": 6, @@ -413,24 +364,24 @@ "dataType": "CpuTotalUsage" }, { - "dataName": "内存使用率", - "dataType": "MemoryUsage" + "dataName": "内存总使用率", + "dataType": "MemoryTotalUsage" }, { - "dataName": "磁盘总使用率", - "dataType": "DiskTotalUsage" + "dataName": "网络接口总体使用率", + "dataType": "InterfaceTotalUtilizationPercentage" } ], - "i": "069299c5-ceec-41cd-a1a8-b18ce0598317", + "i": "ae1f81ab-19dc-44ad-9120-fcdea694d5c4", "x": 0, "y": 0, - "h": 12, + "h": 13, "w": 9, "selectChart": "line", "cardConfig": { "name": { "label": "卡片名称", - "value": "系统信息卡片", + "value": "默认卡片名称", "type": "text" }, "description": { @@ -450,7 +401,9 @@ } }, "moved": false - }, + } + ], + "xl": [ { "type": "card", "h": 15, @@ -603,9 +556,7 @@ } }, "moved": false - } - ], - "sm": [ + }, { "type": "chart", "chartRage": 6, @@ -615,24 +566,24 @@ "dataType": "CpuTotalUsage" }, { - "dataName": "内存使用率", - "dataType": "MemoryUsage" + "dataName": "内存总使用率", + "dataType": "MemoryTotalUsage" }, { - "dataName": "磁盘总使用率", - "dataType": "DiskTotalUsage" + "dataName": "网络接口总体使用率", + "dataType": "InterfaceTotalUtilizationPercentage" } ], - "i": "069299c5-ceec-41cd-a1a8-b18ce0598317", - "x": 0, - "y": 0, - "h": 12, - "w": 9, + "i": "ae1f81ab-19dc-44ad-9120-fcdea694d5c4", + "x": 10, + "y": 29, + "h": 5, + "w": 5, "selectChart": "line", "cardConfig": { "name": { "label": "卡片名称", - "value": "系统信息卡片", + "value": "默认卡片名称", "type": "text" }, "description": { @@ -652,13 +603,15 @@ } }, "moved": false - }, + } + ], + "sm": [ { "type": "card", "h": 16, "w": 3, "x": 0, - "y": 12, + "y": 15, "i": "68af2024-4a21-4351-b1ce-707aeeb3ea9c", "selectCard": "systemInfo", "moved": false @@ -668,7 +621,7 @@ "h": 16, "w": 3, "x": 3, - "y": 12, + "y": 15, "i": "8104448e-6c47-455f-b635-43b3ac75d4df", "selectCard": "onlineUsers", "moved": false @@ -688,7 +641,7 @@ ], "i": "6aa12623-fee2-48e3-86f1-88fcd538c4a8", "x": 3, - "y": 44, + "y": 47, "h": 16, "w": 3, "selectChart": "pie", @@ -735,7 +688,7 @@ ], "i": "17334b32-cede-441f-9bef-772d170f1c2a", "x": 0, - "y": 44, + "y": 47, "h": 16, "w": 3, "selectChart": "histogram", @@ -778,7 +731,7 @@ ], "i": "06e64544-dfb8-4a28-b777-96cb4fd70aec", "x": 0, - "y": 28, + "y": 31, "h": 16, "w": 9, "selectChart": "area", @@ -805,6 +758,53 @@ } }, "moved": false + }, + { + "type": "chart", + "chartRage": 6, + "serverValues": [ + { + "dataName": "CPU总使用率", + "dataType": "CpuTotalUsage" + }, + { + "dataName": "内存总使用率", + "dataType": "MemoryTotalUsage" + }, + { + "dataName": "网络接口总体使用率", + "dataType": "InterfaceTotalUtilizationPercentage" + } + ], + "i": "ae1f81ab-19dc-44ad-9120-fcdea694d5c4", + "x": 0, + "y": 0, + "h": 15, + "w": 6, + "selectChart": "line", + "cardConfig": { + "name": { + "label": "卡片名称", + "value": "默认卡片名称", + "type": "text" + }, + "description": { + "label": "卡片描述", + "value": "这是一张默认的卡片", + "type": "text" + }, + "foreground": { + "label": "卡片前景色", + "value": "ffffff", + "type": "color" + }, + "color": { + "label": "图表主色", + "value": "002EA6", + "type": "color" + } + }, + "moved": false } ], "xs": [ @@ -816,10 +816,6 @@ "dataName": "CPU总使用率", "dataType": "CpuTotalUsage" }, - { - "dataName": "内存使用率", - "dataType": "MemoryUsage" - }, { "dataName": "磁盘总使用率", "dataType": "DiskTotalUsage" @@ -827,8 +823,8 @@ ], "i": "069299c5-ceec-41cd-a1a8-b18ce0598317", "x": 0, - "y": 0, - "h": 12, + "y": 45, + "h": 13, "w": 9, "selectChart": "line", "cardConfig": { @@ -857,10 +853,10 @@ }, { "type": "card", - "h": 16, - "w": 2, - "x": 2, - "y": 12, + "h": 13, + "w": 4, + "x": 0, + "y": 32, "i": "68af2024-4a21-4351-b1ce-707aeeb3ea9c", "selectCard": "systemInfo", "moved": false @@ -868,9 +864,9 @@ { "type": "card", "h": 16, - "w": 2, + "w": 4, "x": 0, - "y": 12, + "y": 16, "i": "8104448e-6c47-455f-b635-43b3ac75d4df", "selectCard": "onlineUsers", "moved": false @@ -889,8 +885,8 @@ } ], "i": "6aa12623-fee2-48e3-86f1-88fcd538c4a8", - "x": 3, - "y": 59, + "x": 0, + "y": 0, "h": 16, "w": 4, "selectChart": "pie", @@ -927,8 +923,8 @@ "dataType": "DiskTotalUsage" }, { - "dataName": "内存使用率", - "dataType": "MemoryUsage" + "dataName": "内存总使用率", + "dataType": "MemoryTotalUsage" }, { "dataName": "CPU总使用率", @@ -937,9 +933,9 @@ ], "i": "17334b32-cede-441f-9bef-772d170f1c2a", "x": 0, - "y": 44, - "h": 15, - "w": 4, + "y": 74, + "h": 16, + "w": 9, "selectChart": "histogram", "cardConfig": { "name": { @@ -980,7 +976,7 @@ ], "i": "06e64544-dfb8-4a28-b777-96cb4fd70aec", "x": 0, - "y": 28, + "y": 58, "h": 16, "w": 9, "selectChart": "area", diff --git a/LoongPanel-Asp/appsettings.json b/LoongPanel-Asp/appsettings.json index 21edd5a..4d2b49a 100755 --- a/LoongPanel-Asp/appsettings.json +++ b/LoongPanel-Asp/appsettings.json @@ -2,7 +2,8 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "None" } }, "AllowedHosts": "*", @@ -19,5 +20,17 @@ "Secret": "p4Qzf/+GPP/XNLalZGCzwlelOl6skiFZscj6iZ6rZZE=", "Issuer": "LoongPanel", "Audience": "LoongPanel", - "PubLicApi": "/Api/Account/SendVerificationCode;/Api/Account/Register;/Api/Account/Login;/Api/Account/VerifyEmailName;" + "PubLicApi": "/Api/Account/Login", + "Kestrel": { + "Endpoints": { + "MyHttpsEndpoint": { + "Url": "https://192.168.0.13:7233", + "ClientCertificateMode": "AllowCertificate", + "Certificate": { + "Path": "./my.pfx", + "Password": "z1377952468zz" + } + } + } + } } diff --git a/LoongPanel-Asp/markdowns/templates/巡检模板1.md b/LoongPanel-Asp/markdowns/templates/巡检模板1.md new file mode 100644 index 0000000..990661d --- /dev/null +++ b/LoongPanel-Asp/markdowns/templates/巡检模板1.md @@ -0,0 +1,31 @@ +# 巡检记录 + +## 第一部分:基本信息 + +- **巡检日期**:2023 年 11 月 8 日 +- **巡检人员**:张三 +- **巡检部门**:设备维护部 + +## 第二部分:巡检项目 + +| 序号 | 巡检项目名称 | 巡检要点 | 巡检内容 | +| ---- | ------------ | -------------------------------------------------- | -------------------------------------------------------------- | +| 1 | 设备运行状况 | 检查设备运行是否平稳,有无异常振动或噪音。 | 详细记录设备运行参数,比较历史数据,分析是否存在异常。 | +| 2 | 安全防护措施 | 检查安全防护设施是否完好,如安全栅栏、警示标志等。 | 确认所有安全设施无损坏,位置正确,且工作人员了解如何正确使用。 | +| 3 | 环境卫生状况 | 检查工作区域是否清洁,有无垃圾或障碍物。 | 清理工作区域,确保无杂物,保持环境整洁。 | +|...|.....|....|...| + +## 第三部分:检查记录 + +| 序号 | 检查项目 | 检查结果 | 异常说明 | 处理措施 | 反馈意见 | +| ---- | -------- | -------- | ---------------- | ------------------------------ | ---------------------- | +| 1 | 外壳 | 正常 | 无 | 无 | 无 | +| 2 | 电源 | 异常 | 设备有轻微振动。 | 已联系维修人员,计划明日检修。 | 建议增加设备维护频率。 | +| 3 | 主机 | 正常 | 无 | 无 | 无 | +|...|.....|....|...|...|...| + +## 第四部分:巡检总结 + +- **巡检总体评价**:本次巡检总体情况良好,除设备 2 存在轻微振动外,其他各项指标均正常。 +- **重点问题处理**:设备 2 的振动问题已安排维修人员进行检修,预计明日完成。 +- **后续工作建议**:建议加强设备日常维护,特别是对老旧设备进行更频繁的检查,以防止潜在的安全隐患。 \ No newline at end of file diff --git a/LoongPanel-Asp/my.pfx b/LoongPanel-Asp/my.pfx new file mode 100644 index 0000000..344a793 Binary files /dev/null and b/LoongPanel-Asp/my.pfx differ diff --git a/web/.env b/web/.env index e468ea0..631bf83 100755 --- a/web/.env +++ b/web/.env @@ -1 +1 @@ -API_SERVER="http://192.168.0.13:5253" \ No newline at end of file +API_SERVER="https://192.168.0.13:7233" \ No newline at end of file diff --git a/web/app.vue b/web/app.vue index b1b48bf..50b543d 100755 --- a/web/app.vue +++ b/web/app.vue @@ -15,4 +15,5 @@ :root { --primary-color: $primary-color } + diff --git a/web/base.scss b/web/base.scss index 48074fd..b68873a 100755 --- a/web/base.scss +++ b/web/base.scss @@ -19,6 +19,8 @@ $light-unfocused-color: $unfocused-color; $dark-text-color: #D3D3D3; $dark-unfocused-color: $unfocused-color; +$border:1px solid rgba(51, 51, 51, 0.17); + $gap: 8px; $padding: 16px; $radius: 8px; diff --git a/web/bun.lockb b/web/bun.lockb new file mode 100644 index 0000000..8168b6e Binary files /dev/null and b/web/bun.lockb differ diff --git a/web/components/AddCard.vue b/web/components/AddCard.vue index bfd3d87..69c5b07 100644 --- a/web/components/AddCard.vue +++ b/web/components/AddCard.vue @@ -1,6 +1,6 @@