Compare commits
3 Commits
Author | SHA1 | Date |
---|---|---|
zwb | 0d801118cc | |
zwb | 30ffdd1782 | |
zwb | 16de25c2ca |
|
@ -1,7 +1,4 @@
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text.Json;
|
|
||||||
using LoongPanel_Asp.Models;
|
|
||||||
using LoongPanel_Asp.utils;
|
using LoongPanel_Asp.utils;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
|
@ -71,7 +68,7 @@ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options
|
||||||
v => v.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList()
|
v => v.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList()
|
||||||
).Metadata.SetValueComparer(new ListOfStringValueComparer());
|
).Metadata.SetValueComparer(new ListOfStringValueComparer());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// 配置 ServerMonitoringData 实体
|
// 配置 ServerMonitoringData 实体
|
||||||
modelBuilder.Entity<ServerMonitoringData>(entity =>
|
modelBuilder.Entity<ServerMonitoringData>(entity =>
|
||||||
|
@ -115,11 +112,14 @@ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options
|
||||||
Name = roleName,
|
Name = roleName,
|
||||||
NormalizedName = roleName.ToUpperInvariant(),
|
NormalizedName = roleName.ToUpperInvariant(),
|
||||||
ApiPermissions =
|
ApiPermissions =
|
||||||
["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","31","32","33","34","35"],
|
[
|
||||||
RouterPermissions = ["1", "3", "4","5","6","7","8","9","10","11","12","13","14","15","16"]
|
"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", "31", "32", "33", "34", "35"
|
||||||
|
],
|
||||||
|
RouterPermissions = ["1", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var apiRouterPermissions = ControllerScanner.GetApiPermissions();
|
var apiRouterPermissions = ControllerScanner.GetApiPermissions();
|
||||||
|
|
||||||
foreach (var permission in apiRouterPermissions)
|
foreach (var permission in apiRouterPermissions)
|
||||||
|
@ -142,16 +142,15 @@ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options
|
||||||
new RotePermission { Id = 7, Name = "Gpu", Router = "^/host/gpu/.+$" },
|
new RotePermission { Id = 7, Name = "Gpu", Router = "^/host/gpu/.+$" },
|
||||||
new RotePermission { Id = 8, Name = "用户详细", Router = "^/userinfo/.+$" },
|
new RotePermission { Id = 8, Name = "用户详细", Router = "^/userinfo/.+$" },
|
||||||
new RotePermission { Id = 9, Name = "用户监测", Router = "^/serveruser/.+$" },
|
new RotePermission { Id = 9, Name = "用户监测", Router = "^/serveruser/.+$" },
|
||||||
new RotePermission { Id = 10, Name = "进程列表", Router = "/host/process" },
|
new RotePermission { Id = 10, Name = "进程列表", Router = "/host/process" },
|
||||||
new RotePermission { Id = 11, Name = "网络连接列表", Router = "/host/networklist" },
|
new RotePermission { Id = 11, Name = "网络连接列表", Router = "/host/networklist" },
|
||||||
new RotePermission { Id = 12, Name = "巡检记录", Router = "/inspectionrecords" },
|
new RotePermission { Id = 12, Name = "巡检记录", Router = "/inspectionrecords" },
|
||||||
new RotePermission { Id = 13, Name = "巡检记录", Router = "^/settings/.+$" },
|
new RotePermission { Id = 13, Name = "巡检记录", Router = "^/settings/.+$" },
|
||||||
new RotePermission { Id = 14, Name = "登录页面修改密码", Router = "^/changepassword/.+$" },
|
new RotePermission { Id = 14, Name = "登录页面修改密码", Router = "^/changepassword/.+$" }
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach (var permission in rotePermissions) modelBuilder.Entity<RotePermission>().HasData(permission);
|
foreach (var permission in rotePermissions) modelBuilder.Entity<RotePermission>().HasData(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ApplicationUser : IdentityUser
|
public class ApplicationUser : IdentityUser
|
||||||
|
@ -161,22 +160,20 @@ public class ApplicationUser : IdentityUser
|
||||||
[MaxLength(255)] public required string Avatar { get; set; }
|
[MaxLength(255)] public required string Avatar { get; set; }
|
||||||
|
|
||||||
[MaxLength(255)] public string? Desc { get; set; }
|
[MaxLength(255)] public string? Desc { get; set; }
|
||||||
|
|
||||||
[MaxLength(255)] public required string Posts { get; set; }
|
[MaxLength(255)] public required string Posts { get; set; }
|
||||||
|
|
||||||
[MaxLength(255)] public string? Address { get; set; }
|
[MaxLength(255)] public string? Address { get; set; }
|
||||||
|
|
||||||
[MaxLength(255)] public DateTime? LastLoginTime { get; set; }
|
[MaxLength(255)] public DateTime? LastLoginTime { get; set; }
|
||||||
|
|
||||||
[MaxLength(255)] public required DateTime CreateDate { get; init; }
|
[MaxLength(255)] public required DateTime CreateDate { get; init; }
|
||||||
|
|
||||||
[MaxLength(255)] public required DateTime ModifiedDate { get; set; }
|
[MaxLength(255)] public required DateTime ModifiedDate { get; set; }
|
||||||
|
|
||||||
[MaxLength(255)] public string? PhysicalAddress { get; set; }
|
[MaxLength(255)] public string? PhysicalAddress { get; set; }
|
||||||
|
|
||||||
[DataType(DataType.DateTime)]
|
[DataType(DataType.DateTime)] public DateTimeOffset? PasswordExpiredDate { get; set; }
|
||||||
public DateTimeOffset? PasswordExpiredDate { get; set; }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ApplicationRole : IdentityRole
|
public class ApplicationRole : IdentityRole
|
||||||
|
@ -209,13 +206,13 @@ public class ServerMonitoringData
|
||||||
|
|
||||||
public DateTime? Time { get; set; }
|
public DateTime? Time { get; set; }
|
||||||
|
|
||||||
public required string ServerId { get; set; }
|
public required string? ServerId { get; set; }
|
||||||
|
|
||||||
public required string? Data { get; set; }
|
public required string? Data { get; set; }
|
||||||
|
|
||||||
public required string DataName { get; set; }
|
public required string DataName { get; set; }
|
||||||
|
|
||||||
public required string? DataType { get; set; }
|
public required string DataType { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ListOfStringValueComparer() : ValueComparer<List<string>>((c1, c2) => c1!.SequenceEqual(c2!),
|
public class ListOfStringValueComparer() : ValueComparer<List<string>>((c1, c2) => c1!.SequenceEqual(c2!),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[CpuTotalUsage_d3YT]
|
[CpuTotalUsage_d3YT]
|
||||||
ValueName=Cpu总使用率
|
ValueName = Cpu总使用率
|
||||||
Description=Cpu总使用率超过20%通知,超过30%警告
|
Description = Cpu总使用率超过20%通知,超过30%警告
|
||||||
Notify=20
|
Notify = 20
|
||||||
Warning=30
|
Warning = 30
|
|
@ -1,4 +1,11 @@
|
||||||
[CpuTotalJob]
|
[DataJob]
|
||||||
|
Group = Data
|
||||||
|
ValueName = 数据库提交
|
||||||
|
Description = A simple job that uses the database
|
||||||
|
JobType = LoongPanel_Asp.Jobs.DataJob, LoongPanel-Asp
|
||||||
|
CronExpression = 0 0/1 * * * ? *
|
||||||
|
|
||||||
|
[CpuTotalJob]
|
||||||
Group = CPU
|
Group = CPU
|
||||||
ValueName = CPU使用率
|
ValueName = CPU使用率
|
||||||
Description = A simple job that uses the CPU
|
Description = A simple job that uses the CPU
|
||||||
|
@ -22,6 +29,15 @@ JobType = LoongPanel_Asp.Jobs.CpuSpeedJob, LoongPanel-Asp
|
||||||
Executor = d3YT,xseg
|
Executor = d3YT,xseg
|
||||||
CronExpression = 2/10 * * * * ? *
|
CronExpression = 2/10 * * * * ? *
|
||||||
|
|
||||||
|
|
||||||
|
[SystemTimeIntervalLoadJob]
|
||||||
|
Group = System
|
||||||
|
ValueName = 系统间隔负载
|
||||||
|
Description = A simple job that uses the CPU
|
||||||
|
JobType = LoongPanel_Asp.Jobs.SystemTimeIntervalLoadJob, LoongPanel-Asp
|
||||||
|
Executor = d3YT,xseg
|
||||||
|
CronExpression = 1/5 * * * * ? *
|
||||||
|
|
||||||
[ProcessTotalJob]
|
[ProcessTotalJob]
|
||||||
Group = Process
|
Group = Process
|
||||||
ValueName = 进程总使用数
|
ValueName = 进程总使用数
|
||||||
|
|
|
@ -28,7 +28,7 @@ public class AccountController(
|
||||||
//判断用户名,邮箱是否唯一
|
//判断用户名,邮箱是否唯一
|
||||||
var user = await userManager.FindByNameAsync(model.UserName);
|
var user = await userManager.FindByNameAsync(model.UserName);
|
||||||
if (user != null) return BadRequest("用户名已存在");
|
if (user != null) return BadRequest("用户名已存在");
|
||||||
|
|
||||||
//创建用户
|
//创建用户
|
||||||
user = new ApplicationUser
|
user = new ApplicationUser
|
||||||
{
|
{
|
||||||
|
@ -39,14 +39,16 @@ public class AccountController(
|
||||||
Email = model.Email,
|
Email = model.Email,
|
||||||
UserName = model.UserName,
|
UserName = model.UserName,
|
||||||
PhoneNumber = model.Phone,
|
PhoneNumber = model.Phone,
|
||||||
NickName = model.FullName,
|
NickName = model.FullName
|
||||||
};
|
};
|
||||||
var result = await userManager.CreateAsync(user, model.Password);
|
var result = await userManager.CreateAsync(user, model.Password);
|
||||||
if (!result.Succeeded) return BadRequest("无法创建用户,"+string.Join(",",result.Errors.ToList().Select(e=>e.Description)));
|
if (!result.Succeeded)
|
||||||
|
return BadRequest("无法创建用户," + string.Join(",", result.Errors.ToList().Select(e => e.Description)));
|
||||||
|
|
||||||
//添加用户到默认角色
|
//添加用户到默认角色
|
||||||
result = await userManager.AddToRoleAsync(user, model.Role);
|
result = await userManager.AddToRoleAsync(user, model.Role);
|
||||||
if (!result.Succeeded) return BadRequest("无法创建用户,"+string.Join(",",result.Errors.ToList().Select(e=>e.Description)));
|
if (!result.Succeeded)
|
||||||
|
return BadRequest("无法创建用户," + string.Join(",", result.Errors.ToList().Select(e => e.Description)));
|
||||||
return Ok("用户创建成功");
|
return Ok("用户创建成功");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
@ -55,7 +57,7 @@ public class AccountController(
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpPost("Login")]
|
[HttpPost("Login")]
|
||||||
public async Task<IActionResult> Login([FromBody] LoginModel model)
|
public async Task<IActionResult> Login([FromBody] LoginModel model)
|
||||||
|
@ -67,31 +69,25 @@ public class AccountController(
|
||||||
else
|
else
|
||||||
user = await userManager.FindByNameAsync(model.EmailOrUserName);
|
user = await userManager.FindByNameAsync(model.EmailOrUserName);
|
||||||
if (user == null) return BadRequest("用户不存在");
|
if (user == null) return BadRequest("用户不存在");
|
||||||
var result = await signInManager.CheckPasswordSignInAsync(user, model.Password,true);
|
var result = await signInManager.CheckPasswordSignInAsync(user, model.Password, true);
|
||||||
if (!result.Succeeded) return BadRequest("错误账号或密码");
|
if (!result.Succeeded) return BadRequest("错误账号或密码");
|
||||||
if (await userManager.IsLockedOutAsync(user))
|
if (await userManager.IsLockedOutAsync(user)) return BadRequest("账号已锁定,请联系管理员");
|
||||||
{
|
if (!user.EmailConfirmed && user.UserName != "admin")
|
||||||
return BadRequest("账号已锁定,请联系管理员");
|
|
||||||
}
|
|
||||||
if (!user.EmailConfirmed&&user.UserName!="admin")
|
|
||||||
{
|
|
||||||
return Unauthorized(new
|
return Unauthorized(new
|
||||||
{
|
{
|
||||||
user.Email,
|
user.Email,
|
||||||
user.Id
|
user.Id
|
||||||
});
|
});
|
||||||
}
|
|
||||||
//创建修改密码令牌
|
//创建修改密码令牌
|
||||||
var tokenPassword = await userManager.GeneratePasswordResetTokenAsync(user);
|
var tokenPassword = await userManager.GeneratePasswordResetTokenAsync(user);
|
||||||
if ((user.PasswordExpiredDate == null || user.PasswordExpiredDate < DateTimeOffset.Now)&&user.UserName!="admin")
|
if ((user.PasswordExpiredDate == null || user.PasswordExpiredDate < DateTimeOffset.Now) &&
|
||||||
{
|
user.UserName != "admin")
|
||||||
//返回402
|
//返回402
|
||||||
return StatusCode(402,new
|
return StatusCode(402, new
|
||||||
{
|
{
|
||||||
tokenPassword,
|
tokenPassword,
|
||||||
user.Id,
|
user.Id
|
||||||
});
|
});
|
||||||
}
|
|
||||||
var roles = await userManager.GetRolesAsync(user);
|
var roles = await userManager.GetRolesAsync(user);
|
||||||
var roleId = roles.ToList()[0]; // 直接获取角色ID列表
|
var roleId = roles.ToList()[0]; // 直接获取角色ID列表
|
||||||
var claimsIdentity = new ClaimsIdentity(new[]
|
var claimsIdentity = new ClaimsIdentity(new[]
|
||||||
|
@ -101,21 +97,22 @@ public class AccountController(
|
||||||
new Claim(ClaimTypes.Role, roleId.ToLower()) // 将角色ID列表转换为逗号分隔的字符串
|
new Claim(ClaimTypes.Role, roleId.ToLower()) // 将角色ID列表转换为逗号分隔的字符串
|
||||||
});
|
});
|
||||||
var token = tokenHelper.GenerateToken(claimsIdentity);
|
var token = tokenHelper.GenerateToken(claimsIdentity);
|
||||||
|
|
||||||
return Ok(new
|
return Ok(new
|
||||||
{
|
{
|
||||||
user.NickName,
|
user.NickName,
|
||||||
Token=token
|
Token = token
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("VerifyEmail")]
|
[HttpGet("VerifyEmail")]
|
||||||
public async Task<IActionResult> VerifyEmail( [FromQuery] string userId,[FromQuery] string email ,[FromQuery] string? code = null)
|
public async Task<IActionResult> VerifyEmail([FromQuery] string userId, [FromQuery] string email,
|
||||||
|
[FromQuery] string? code = null)
|
||||||
{
|
{
|
||||||
//如果code 不为空
|
//如果code 不为空
|
||||||
if (code != null)
|
if (code != null)
|
||||||
{
|
{
|
||||||
var r= emailService.VerifyEmailVerifyCode(userId,code);
|
var r = emailService.VerifyEmailVerifyCode(userId, code);
|
||||||
if (!r) return BadRequest("验证码错误");
|
if (!r) return BadRequest("验证码错误");
|
||||||
//验证成功
|
//验证成功
|
||||||
//更新用户信息
|
//更新用户信息
|
||||||
|
@ -126,31 +123,28 @@ public class AccountController(
|
||||||
await userManager.UpdateAsync(user);
|
await userManager.UpdateAsync(user);
|
||||||
return Ok("邮箱验证成功");
|
return Ok("邮箱验证成功");
|
||||||
}
|
}
|
||||||
await emailService.SendEmailVerifyCodeAsync( userId,email, "尊敬的用户");
|
|
||||||
|
await emailService.SendEmailVerifyCodeAsync(userId, email, "尊敬的用户");
|
||||||
return Ok("邮件已发送");
|
return Ok("邮件已发送");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("ChangePassword")]
|
[HttpGet("ChangePassword")]
|
||||||
public async Task<IActionResult> ChangePassword([FromQuery] string userId, [FromQuery] string token,[FromQuery] string newPassword)
|
public async Task<IActionResult> ChangePassword([FromQuery] string userId, [FromQuery] string token,
|
||||||
|
[FromQuery] string newPassword)
|
||||||
{
|
{
|
||||||
// 获取当前经过身份验证的用户
|
// 获取当前经过身份验证的用户
|
||||||
var authenticatedUser = await userManager.FindByIdAsync(userId);
|
var authenticatedUser = await userManager.FindByIdAsync(userId);
|
||||||
if (authenticatedUser == null)
|
if (authenticatedUser == null) return BadRequest("用户不存在");
|
||||||
{
|
|
||||||
return BadRequest("用户不存在");
|
|
||||||
}
|
|
||||||
// 重置密码
|
// 重置密码
|
||||||
var result = await userManager.ResetPasswordAsync(authenticatedUser, token, newPassword);
|
var result = await userManager.ResetPasswordAsync(authenticatedUser, token, newPassword);
|
||||||
|
|
||||||
if (!result.Succeeded)
|
if (!result.Succeeded) return BadRequest("修改密码失败");
|
||||||
{
|
|
||||||
return BadRequest("修改密码失败");
|
|
||||||
}
|
|
||||||
//修改用户的过期时间为1天后
|
//修改用户的过期时间为1天后
|
||||||
authenticatedUser.PasswordExpiredDate = DateTimeOffset.Now.AddDays(1);
|
authenticatedUser.PasswordExpiredDate = DateTimeOffset.Now.AddDays(1);
|
||||||
await userManager.UpdateAsync(authenticatedUser);
|
await userManager.UpdateAsync(authenticatedUser);
|
||||||
return Ok("修改密码成功");
|
return Ok("修改密码成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("ForgotPassword")]
|
[HttpGet("ForgotPassword")]
|
||||||
public async Task<IActionResult> ForgotPassword([FromQuery] string email)
|
public async Task<IActionResult> ForgotPassword([FromQuery] string email)
|
||||||
{
|
{
|
||||||
|
@ -168,7 +162,7 @@ public class AccountController(
|
||||||
var user = await userManager.FindByEmailAsync(email);
|
var user = await userManager.FindByEmailAsync(email);
|
||||||
if (user == null) return BadRequest("用户不存在");
|
if (user == null) return BadRequest("用户不存在");
|
||||||
//随机生成密码12位 特殊字符大小写数字 英文
|
//随机生成密码12位 特殊字符大小写数字 英文
|
||||||
token= token.Replace(" ", "+");
|
token = token.Replace(" ", "+");
|
||||||
var newPassword = GenerateRandomPassword(32);
|
var newPassword = GenerateRandomPassword(32);
|
||||||
var result = await userManager.ResetPasswordAsync(user, token, newPassword);
|
var result = await userManager.ResetPasswordAsync(user, token, newPassword);
|
||||||
if (!result.Succeeded) return BadRequest(result.Errors.FirstOrDefault()?.Description);
|
if (!result.Succeeded) return BadRequest(result.Errors.FirstOrDefault()?.Description);
|
||||||
|
@ -179,7 +173,7 @@ public class AccountController(
|
||||||
$"<h1>您的新密码是:<strong>{newPassword}</strong></h1>");
|
$"<h1>您的新密码是:<strong>{newPassword}</strong></h1>");
|
||||||
return Ok("密码重置成功,新密码已生成并发送给用户邮箱。");
|
return Ok("密码重置成功,新密码已生成并发送给用户邮箱。");
|
||||||
}
|
}
|
||||||
|
|
||||||
//登出
|
//登出
|
||||||
[HttpGet("Logout")]
|
[HttpGet("Logout")]
|
||||||
public async Task<IActionResult> Logout()
|
public async Task<IActionResult> Logout()
|
||||||
|
@ -187,6 +181,7 @@ public class AccountController(
|
||||||
await signInManager.SignOutAsync();
|
await signInManager.SignOutAsync();
|
||||||
return Ok("Logout successful");
|
return Ok("Logout successful");
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GenerateRandomPassword(int length)
|
private string GenerateRandomPassword(int length)
|
||||||
{
|
{
|
||||||
const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&*";
|
const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&*";
|
||||||
|
@ -195,6 +190,7 @@ public class AccountController(
|
||||||
{
|
{
|
||||||
rng.GetBytes(byteBuffer);
|
rng.GetBytes(byteBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new string(byteBuffer.Select(b => chars[b % chars.Length]).ToArray());
|
return new string(byteBuffer.Select(b => chars[b % chars.Length]).ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using IniParser;
|
using IniParser;
|
||||||
using IniParser.Model;
|
|
||||||
using LoongPanel_Asp.Helpers;
|
using LoongPanel_Asp.Helpers;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
@ -8,22 +7,23 @@ namespace LoongPanel_Asp.Controllers;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("Api/[controller]")]
|
[Route("Api/[controller]")]
|
||||||
public class ConfigController:ControllerBase
|
public class ConfigController : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpGet("GetAlertConfig")]
|
[HttpGet("GetAlertConfig")]
|
||||||
public IActionResult GetAlertConfig()
|
public IActionResult GetAlertConfig()
|
||||||
{
|
{
|
||||||
var userId = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)!.Value;
|
var userId = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)!.Value;
|
||||||
//从Configs/Alerts/userId.ini
|
//从Configs/Alerts/userId.ini
|
||||||
var parser=new FileIniDataParser();
|
var parser = new FileIniDataParser();
|
||||||
var fullPath=Path.Combine("Configs","Alerts",$"{userId}.ini");
|
var fullPath = Path.Combine("Configs", "Alerts", $"{userId}.ini");
|
||||||
//判断文件是否存在
|
//判断文件是否存在
|
||||||
if (!System.IO.File.Exists(fullPath))
|
if (!System.IO.File.Exists(fullPath))
|
||||||
{
|
{
|
||||||
//复制一份默认配置文件
|
//复制一份默认配置文件
|
||||||
System.IO.File.Copy(Path.Combine("Configs","alert.ini"),fullPath);
|
System.IO.File.Copy(Path.Combine("Configs", "alert.ini"), fullPath);
|
||||||
JobConfigHelper.ReloadAlerts();
|
JobConfigHelper.ReloadAlerts();
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = parser.ReadFile(fullPath);
|
var data = parser.ReadFile(fullPath);
|
||||||
//获得所有section
|
//获得所有section
|
||||||
var sections = data.Sections;
|
var sections = data.Sections;
|
||||||
|
@ -34,10 +34,10 @@ public class ConfigController:ControllerBase
|
||||||
{
|
{
|
||||||
AlertType = x.SectionName.Split("_")[0],
|
AlertType = x.SectionName.Split("_")[0],
|
||||||
ServerId = x.SectionName.Split("_")[1],
|
ServerId = x.SectionName.Split("_")[1],
|
||||||
Notify= x.Keys["Notify"],
|
Notify = x.Keys["Notify"],
|
||||||
Warning=x.Keys["Warning"],
|
Warning = x.Keys["Warning"],
|
||||||
TypeName=x.Keys["ValueName"],
|
TypeName = x.Keys["ValueName"],
|
||||||
Description=x.Keys["Description"],
|
Description = x.Keys["Description"]
|
||||||
};
|
};
|
||||||
return alertConfiguration;
|
return alertConfiguration;
|
||||||
});
|
});
|
||||||
|
@ -48,37 +48,32 @@ public class ConfigController:ControllerBase
|
||||||
public IActionResult AddAlertConfig([FromBody] AlertModel model)
|
public IActionResult AddAlertConfig([FromBody] AlertModel model)
|
||||||
{
|
{
|
||||||
var userId = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)!.Value;
|
var userId = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)!.Value;
|
||||||
var fullPath=Path.Combine("Configs","Alerts",$"{userId}.ini");
|
var fullPath = Path.Combine("Configs", "Alerts", $"{userId}.ini");
|
||||||
var parser=new FileIniDataParser();
|
var parser = new FileIniDataParser();
|
||||||
var data = parser.ReadFile(fullPath);
|
var data = parser.ReadFile(fullPath);
|
||||||
//判断是否存在
|
//判断是否存在
|
||||||
if (data.Sections.Contains($"{model.DataType}_{model.ServerId}"))
|
if (data.Sections.Contains($"{model.DataType}_{model.ServerId}")) return BadRequest("配置已存在");
|
||||||
{
|
var sectionName = $"{model.DataType}_{model.ServerId}";
|
||||||
return BadRequest("配置已存在");
|
data[sectionName]["Notify"] = model.Notify;
|
||||||
}
|
data[sectionName]["Warning"] = model.Warning;
|
||||||
var sectionName=$"{model.DataType}_{model.ServerId}";
|
data[sectionName]["ValueName"] = model.DataName;
|
||||||
data[sectionName]["Notify"]=model.Notify;
|
data[sectionName]["Description"] = model.Description;
|
||||||
data[sectionName]["Warning"]=model.Warning;
|
parser.WriteFile(fullPath, data);
|
||||||
data[sectionName]["ValueName"]=model.DataName;
|
|
||||||
data[sectionName]["Description"]=model.Description;
|
|
||||||
parser.WriteFile(fullPath,data);
|
|
||||||
JobConfigHelper.ReloadAlerts();
|
JobConfigHelper.ReloadAlerts();
|
||||||
return Ok("配置已添加");
|
return Ok("配置已添加");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("DeleteAlertConfig")]
|
[HttpDelete("DeleteAlertConfig")]
|
||||||
public IActionResult DeleteAlertConfig([FromQuery] string dataType,[FromQuery] string serverId)
|
public IActionResult DeleteAlertConfig([FromQuery] string dataType, [FromQuery] string serverId)
|
||||||
{
|
{
|
||||||
var userId = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)!.Value;
|
var userId = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)!.Value;
|
||||||
var fullPath=Path.Combine("Configs","Alerts",$"{userId}.ini");
|
var fullPath = Path.Combine("Configs", "Alerts", $"{userId}.ini");
|
||||||
var parser=new FileIniDataParser();
|
var parser = new FileIniDataParser();
|
||||||
var data = parser.ReadFile(fullPath);
|
var data = parser.ReadFile(fullPath);
|
||||||
//判断是否存在
|
//判断是否存在
|
||||||
if (!data.Sections.Contains($"{dataType}_{serverId}"))
|
if (!data.Sections.Contains($"{dataType}_{serverId}")) return BadRequest("配置不存在");
|
||||||
{
|
|
||||||
return BadRequest("配置不存在");
|
|
||||||
}
|
|
||||||
data.Sections.RemoveSection($"{dataType}_{serverId}");
|
data.Sections.RemoveSection($"{dataType}_{serverId}");
|
||||||
parser.WriteFile(fullPath,data);
|
parser.WriteFile(fullPath, data);
|
||||||
JobConfigHelper.ReloadAlerts();
|
JobConfigHelper.ReloadAlerts();
|
||||||
return Ok("配置已删除");
|
return Ok("配置已删除");
|
||||||
}
|
}
|
||||||
|
@ -86,11 +81,11 @@ public class ConfigController:ControllerBase
|
||||||
|
|
||||||
public class AlertModel
|
public class AlertModel
|
||||||
{
|
{
|
||||||
public required string ServerId {get;init;}
|
public required string ServerId { get; init; }
|
||||||
public required string DataType {get;init;}
|
public required string DataType { get; init; }
|
||||||
public required string Description {get;init;}
|
public required string Description { get; init; }
|
||||||
public required string Notify {get;init;}
|
public required string Notify { get; init; }
|
||||||
public required string Warning {get;init;}
|
public required string Warning { get; init; }
|
||||||
|
|
||||||
public required string DataName {get;init;}
|
public required string DataName { get; init; }
|
||||||
}
|
}
|
|
@ -8,23 +8,34 @@ namespace LoongPanel_Asp.Controllers;
|
||||||
public class JobController(ApplicationDbContext dbContext) : ControllerBase
|
public class JobController(ApplicationDbContext dbContext) : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpGet("GetJobList")]
|
[HttpGet("GetJobList")]
|
||||||
public IActionResult GetJobList([FromQuery]string? serverId=null)
|
public IActionResult GetJobList([FromQuery] string? serverId = null)
|
||||||
{
|
{
|
||||||
var wildcardRules = new Dictionary<string, List<string>>
|
var wildcardRules = new Dictionary<string, List<string>>
|
||||||
{
|
{
|
||||||
{ "CPU总体参数", ["CpuTotalUsage","CpuTotalSpeed","CpuIOWaitUsage","CpuSystemUsage","CpuUserUsage"] },
|
{ "CPU总体参数", ["CpuTotalUsage", "CpuTotalSpeed", "CpuIOWaitUsage", "CpuSystemUsage", "CpuUserUsage"] },
|
||||||
{ "CPU单核参数", ["CpuSingleSpeed-*"] },
|
{ "CPU单核参数", ["CpuSingleSpeed-*"] },
|
||||||
{ "CPU用户参数", ["CpuUsage-*"] },
|
{ "CPU用户参数", ["CpuUsage-*"] },
|
||||||
{ "内存总体参数", ["MemoryCache","MemoryFree","MemoryTotal","MemoryTotalUsage","MemoryUsed"] },
|
{ "内存总体参数", ["MemoryCache", "MemoryFree", "MemoryTotal", "MemoryTotalUsage", "MemoryUsed"] },
|
||||||
{ "内存用户参数", ["MemoryUsage-*"] },
|
{ "内存用户参数", ["MemoryUsage-*"] },
|
||||||
{ "Swap参数", ["SwapFree","SwapTotal","SwapTotalUsage","SwapUsed"] },
|
{ "Swap参数", ["SwapFree", "SwapTotal", "SwapTotalUsage", "SwapUsed"] },
|
||||||
{ "各个磁盘参数", ["DiskTps-*","DiskTps-*","DiskUtil-*","DiskWriteKB-*","DiskWriteKB-*","DiskAwait-*","DiskReadKB-*"] },
|
{
|
||||||
|
"各个磁盘参数",
|
||||||
|
[
|
||||||
|
"DiskTps-*", "DiskTps-*", "DiskUtil-*", "DiskWriteKB-*", "DiskWriteKB-*", "DiskAwait-*",
|
||||||
|
"DiskReadKB-*"
|
||||||
|
]
|
||||||
|
},
|
||||||
{ "磁盘总体参数", ["DiskTotalUsage"] },
|
{ "磁盘总体参数", ["DiskTotalUsage"] },
|
||||||
{ "网络总体参数", ["InterfaceTotalUtilizationPercentage"] },
|
{ "网络总体参数", ["InterfaceTotalUtilizationPercentage"] },
|
||||||
{ "各个网络接口参数", ["InterfaceUtilizationPercentage-*","InterfaceUtilizationPercentage-*","ReceivedPacketsPerSecond-*","TransmittedPacketsPerSecond-*"] },
|
{
|
||||||
{ "进程", ["PhrasePatternCount","ProcessTotalCount","ThreadsTotalCount"] },
|
"各个网络接口参数",
|
||||||
{ "用户进程", ["UserProcesses-*"] },
|
[
|
||||||
|
"InterfaceUtilizationPercentage-*", "InterfaceUtilizationPercentage-*",
|
||||||
|
"ReceivedPacketsPerSecond-*", "TransmittedPacketsPerSecond-*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "进程", ["PhrasePatternCount", "ProcessTotalCount", "ThreadsTotalCount"] },
|
||||||
|
{ "用户进程", ["UserProcesses-*"] }
|
||||||
};
|
};
|
||||||
var filteredData = dbContext.ServerMonitoringData
|
var filteredData = dbContext.ServerMonitoringData
|
||||||
.Where(x => string.IsNullOrEmpty(serverId) || x.ServerId == serverId)
|
.Where(x => string.IsNullOrEmpty(serverId) || x.ServerId == serverId)
|
||||||
|
@ -36,21 +47,22 @@ public class JobController(ApplicationDbContext dbContext) : ControllerBase
|
||||||
{
|
{
|
||||||
x.DataName,
|
x.DataName,
|
||||||
x.DataType,
|
x.DataType,
|
||||||
GroupName = wildcardRules.FirstOrDefault(rule => x.DataType != null && IsMatch(x.DataType, rule.Value)).Key ?? "其他"
|
GroupName = wildcardRules.FirstOrDefault(rule => x.DataType != null && IsMatch(x.DataType, rule.Value))
|
||||||
|
.Key ?? "其他"
|
||||||
})
|
})
|
||||||
.GroupBy(x => x.GroupName)
|
.GroupBy(x => x.GroupName)
|
||||||
.Select(group => new
|
.Select(group => new
|
||||||
{
|
{
|
||||||
GroupName=group.Key,
|
GroupName = group.Key,
|
||||||
Items = group.ToList()
|
Items = group.ToList()
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
return Ok(groupedData);
|
return Ok(groupedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsMatch(string input, List<string> patterns)
|
private bool IsMatch(string input, List<string> patterns)
|
||||||
{
|
{
|
||||||
return patterns.Select(pattern => "^" + Regex.Escape(pattern).Replace("\\*", ".*") + "$").Any(regexPattern => Regex.IsMatch(input, regexPattern));
|
return patterns.Select(pattern => "^" + Regex.Escape(pattern).Replace("\\*", ".*") + "$")
|
||||||
|
.Any(regexPattern => Regex.IsMatch(input, regexPattern));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace LoongPanel_Asp.Controllers;
|
namespace LoongPanel_Asp.Controllers;
|
||||||
|
|
||||||
|
@ -10,22 +9,13 @@ public class PublicFileController(IWebHostEnvironment webHostEnvironment) : Cont
|
||||||
[HttpPost("UploadImage")]
|
[HttpPost("UploadImage")]
|
||||||
public async Task<IActionResult> UploadImage(IFormFile? file)
|
public async Task<IActionResult> UploadImage(IFormFile? file)
|
||||||
{
|
{
|
||||||
if (file == null || file.Length == 0)
|
if (file == null || file.Length == 0) return BadRequest("文件不能为空");
|
||||||
{
|
|
||||||
return BadRequest("文件不能为空");
|
|
||||||
}
|
|
||||||
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif" };
|
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif" };
|
||||||
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
|
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
|
||||||
Console.WriteLine(extension);
|
Console.WriteLine(extension);
|
||||||
if (!allowedExtensions.Contains(extension))
|
if (!allowedExtensions.Contains(extension)) return BadRequest("不支持的文件类型");
|
||||||
{
|
|
||||||
return BadRequest("不支持的文件类型");
|
|
||||||
}
|
|
||||||
var uploadsFolderPath = Path.Combine(webHostEnvironment.WebRootPath, "public/image");
|
var uploadsFolderPath = Path.Combine(webHostEnvironment.WebRootPath, "public/image");
|
||||||
if (!Directory.Exists(uploadsFolderPath))
|
if (!Directory.Exists(uploadsFolderPath)) Directory.CreateDirectory(uploadsFolderPath);
|
||||||
{
|
|
||||||
Directory.CreateDirectory(uploadsFolderPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
var uniqueFileName = $"{Guid.NewGuid()}{extension}";
|
var uniqueFileName = $"{Guid.NewGuid()}{extension}";
|
||||||
var filePath = Path.Combine(uploadsFolderPath, uniqueFileName);
|
var filePath = Path.Combine(uploadsFolderPath, uniqueFileName);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using LoongPanel_Asp.Helpers;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
@ -20,16 +19,14 @@ public class RoteController(
|
||||||
var role = await roleManager.FindByNameAsync(roleId);
|
var role = await roleManager.FindByNameAsync(roleId);
|
||||||
var rotes = role!.RouterPermissions.ToList();
|
var rotes = role!.RouterPermissions.ToList();
|
||||||
//获取路由列表
|
//获取路由列表
|
||||||
var apiPermissions = dbContext.RotePermissions.ToList().Where(x => rotes.Any(y => y == x.Id.ToString())).Select(x=>x.Router).ToList();
|
var apiPermissions = dbContext.RotePermissions.ToList().Where(x => rotes.Any(y => y == x.Id.ToString()))
|
||||||
|
.Select(x => x.Router).ToList();
|
||||||
//如果rotes 中包括*
|
//如果rotes 中包括*
|
||||||
if (rotes.Contains("*"))
|
if (rotes.Contains("*")) return Ok("权限验证通过");
|
||||||
{
|
|
||||||
return Ok("权限验证通过");
|
|
||||||
}
|
|
||||||
//将path全部小写
|
//将path全部小写
|
||||||
path = path.ToLower();
|
path = path.ToLower();
|
||||||
//使用正则匹配
|
//使用正则匹配
|
||||||
var firstOrDefault = apiPermissions.FirstOrDefault(x => Regex.IsMatch(path,x));
|
var firstOrDefault = apiPermissions.FirstOrDefault(x => Regex.IsMatch(path, x));
|
||||||
return Ok(firstOrDefault != null ? Ok("权限验证通过") : Unauthorized("你不具有访问此资源的权力"));
|
return Ok(firstOrDefault != null ? Ok("权限验证通过") : Unauthorized("你不具有访问此资源的权力"));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,29 +1,29 @@
|
||||||
using System.Collections.Concurrent;
|
using System.Dynamic;
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.Dynamic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Net;
|
using System.Security.Claims;
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using LoongPanel_Asp.Helpers;
|
using LoongPanel_Asp.Helpers;
|
||||||
using LoongPanel_Asp.Models;
|
using LoongPanel_Asp.Hubs;
|
||||||
using LoongPanel_Asp.Servers;
|
using LoongPanel_Asp.Servers;
|
||||||
using Microsoft.AspNetCore.Http.HttpResults;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace LoongPanel_Asp.Controllers;
|
namespace LoongPanel_Asp.Controllers;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("Api/[controller]")]
|
[Route("Api/[controller]")]
|
||||||
public class ServerController(IServiceProvider serviceProvider, ApplicationDbContext dbContext) : ControllerBase
|
public class ServerController(
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
ApplicationDbContext dbContext,
|
||||||
|
IHubContext<SessionHub> hubContext) : ControllerBase
|
||||||
{
|
{
|
||||||
//获取远程服务器地址列表
|
//获取远程服务器地址列表
|
||||||
[HttpGet("GetServerList")]
|
[HttpGet("GetServerList")]
|
||||||
public IActionResult GetServerList()
|
public IActionResult GetServerList()
|
||||||
{
|
{
|
||||||
var serverConfigs=JobConfigHelper.GetServers();
|
var serverConfigs = JobConfigHelper.GetServers();
|
||||||
//读取每一个配置项转换为列表
|
//读取每一个配置项转换为列表
|
||||||
var serverList = serverConfigs.Select(section => new ServerInfo
|
var serverList = serverConfigs.Select(section => new ServerInfo
|
||||||
{
|
{
|
||||||
|
@ -51,64 +51,36 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
|
||||||
public async Task<IActionResult> GetServerHistoryDate([FromQuery] string serverId,
|
public async Task<IActionResult> GetServerHistoryDate([FromQuery] string serverId,
|
||||||
[FromQuery] List<string?>? dataTypes = null, [FromQuery] int? startIndex = 0)
|
[FromQuery] List<string?>? dataTypes = null, [FromQuery] int? startIndex = 0)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(serverId)) return BadRequest("ServerId is required.");
|
var userId = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)!.Value;
|
||||||
|
//创建查询 流式
|
||||||
var query = dbContext.ServerMonitoringData
|
var query = dbContext.ServerMonitoringData
|
||||||
.Where(s => s.ServerId == serverId && s.DataType != null);
|
.AsNoTracking() // 不追踪实体状态
|
||||||
|
.Where(x => x.ServerId == serverId) // 筛选服务器ID
|
||||||
if (dataTypes != null && dataTypes.Any()) query = query.Where(s => dataTypes.Contains(s.DataType));
|
.Where(x => dataTypes == null || dataTypes.Contains(x.DataType)) // 如果dataTypes不为空,则进一步筛选
|
||||||
|
.OrderBy(x => x.Time)
|
||||||
var allData = await query
|
.Skip(Math.Max(startIndex ?? 0, 0)) // 跳过指定数量的记录
|
||||||
.OrderByDescending(s => s.Time)
|
|
||||||
.Skip(startIndex ?? 0)
|
|
||||||
.Take(1000)
|
.Take(1000)
|
||||||
.ToListAsync();
|
.AsAsyncEnumerable(); // 启用流式查询
|
||||||
|
Dictionary<string, List<List<object>>> temp = new();
|
||||||
|
|
||||||
if (allData.Count == 0) return Ok(new { done = true });
|
await foreach (var data in query)
|
||||||
|
|
||||||
// 获取时间集合并排序
|
|
||||||
var timeList = allData.Select(s => s.Time).Distinct().OrderBy(s => s).ToList();
|
|
||||||
|
|
||||||
// 获取DataType
|
|
||||||
var groupedData = new ConcurrentDictionary<string, List<double>>();
|
|
||||||
var distinctDataTypes = dataTypes ?? allData.Select(s => s.DataType).Distinct().ToList();
|
|
||||||
|
|
||||||
foreach (var dataType in distinctDataTypes)
|
|
||||||
{
|
{
|
||||||
var dataList = allData.Where(s => s.DataType == dataType).ToList();
|
if (!temp.TryGetValue(data.DataType, out var value))
|
||||||
var backDataList = new List<double>();
|
|
||||||
var temp = double.Parse(dataList[0].Data ?? string.Empty);
|
|
||||||
foreach (var time in timeList)
|
|
||||||
{
|
{
|
||||||
var currentData = dataList.Where(d => d.Time == time).ToList();
|
value = new List<List<object>>();
|
||||||
if (currentData.Count > 0)
|
temp[data.DataType] = value;
|
||||||
{
|
|
||||||
//计算data平均值
|
|
||||||
var dataSum = currentData.Sum(d => double.Parse(d.Data ?? string.Empty));
|
|
||||||
temp = dataSum;
|
|
||||||
}
|
|
||||||
|
|
||||||
backDataList.Add(temp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dataType != null) groupedData[dataType] = backDataList;
|
var unixTimestamp = ((DateTimeOffset)data.Time?.ToUniversalTime()!).ToUnixTimeMilliseconds();
|
||||||
|
value.Add([unixTimestamp!, data.Data!]);
|
||||||
}
|
}
|
||||||
|
|
||||||
//timelist转换为当前时间 月 日 分 秒
|
foreach (var entry in temp)
|
||||||
var timeList2 = timeList.Select(time => time?.ToLocalTime().ToString("MM-dd HH:mm:ss")).ToList();
|
|
||||||
|
|
||||||
// 返回结果
|
|
||||||
|
|
||||||
var returnValue = new
|
|
||||||
{
|
{
|
||||||
times = timeList2,
|
await hubContext.Clients.Groups(userId).SendAsync("ReceiveDataHistory", entry.Key, entry.Value, true);
|
||||||
data = groupedData,
|
}
|
||||||
endIndex = dataTypes is { Count: > 0 } ? startIndex + timeList2.Count * dataTypes.Count :
|
|
||||||
startIndex + allData.Count < 1000 ? allData.Count : 1000,
|
return Ok();
|
||||||
done = false
|
|
||||||
};
|
|
||||||
|
|
||||||
return Ok(returnValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -121,63 +93,16 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
|
||||||
return Ok(output);
|
return Ok(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("GetServerHistoryStep")]
|
|
||||||
public async Task<IActionResult> GetServerHistoryStep([FromQuery] string serverId, [FromQuery] string dataType,
|
|
||||||
[FromQuery] string timeRange)
|
|
||||||
{
|
|
||||||
var query = dbContext.ServerMonitoringData
|
|
||||||
.Where(s => s.ServerId == serverId && s.DataType != null && s.DataType == dataType);
|
|
||||||
int timeSpan;
|
|
||||||
int timeNum;
|
|
||||||
switch (timeRange.ToLower())
|
|
||||||
{
|
|
||||||
case "1m":
|
|
||||||
timeSpan = 1;
|
|
||||||
timeNum = 60;
|
|
||||||
break;
|
|
||||||
case "1h":
|
|
||||||
timeSpan = 60;
|
|
||||||
timeNum = 60;
|
|
||||||
break;
|
|
||||||
case "1d":
|
|
||||||
timeSpan = 60 * 12;
|
|
||||||
timeNum = 12;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return BadRequest("Invalid time range.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var time = DateTime.UtcNow;
|
|
||||||
var timeList = new List<DateTime>();
|
|
||||||
var dataList = new List<string>();
|
|
||||||
for (var i = 0; i < timeNum; i++)
|
|
||||||
{
|
|
||||||
var endTime = time.AddSeconds(-(timeSpan * i) + 3 * timeSpan);
|
|
||||||
var startTime = time.AddSeconds(-(timeSpan * i) - 3 * timeSpan);
|
|
||||||
var data = query.FirstOrDefault(d => d.Time <= endTime && d.Time >= startTime);
|
|
||||||
var value = "0";
|
|
||||||
if (data is { Data: not null }) value = data.Data;
|
|
||||||
timeList.Add(startTime);
|
|
||||||
dataList.Add(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
dataList.Reverse();
|
|
||||||
var backData = new
|
|
||||||
{
|
|
||||||
data = dataList,
|
|
||||||
time = timeList.Select(dateTime => dateTime.ToLocalTime().ToString("MM-dd HH:mm:ss")).ToList()
|
|
||||||
};
|
|
||||||
return Ok(backData);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("GetServerMemoryInfo")]
|
[HttpGet("GetServerMemoryInfo")]
|
||||||
public async Task<IActionResult> GetServerMemoryInfo([FromQuery] string serverId)
|
public async Task<IActionResult> GetServerMemoryInfo([FromQuery] string serverId)
|
||||||
{
|
{
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
var sshClient = serviceProvider.GetService<SshService>();
|
||||||
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
||||||
var server = serverConfigs.Find(x =>x.Id == serverId);
|
var server = serverConfigs.Find(x => x.Id == serverId);
|
||||||
if (server == null) return BadRequest();
|
if (server == null) return BadRequest();
|
||||||
var output = await sshClient?.ExecuteCommandAsync(false, serverId,"echo",$"'{server.Password}'","|","sudo","-S","lshw","-class","memory","-json")!;
|
var output = await sshClient?.ExecuteCommandAsync(false, serverId, "echo", $"'{server.Password}'", "|", "sudo",
|
||||||
|
"-S", "lshw", "-class", "memory", "-json")!;
|
||||||
if (string.IsNullOrEmpty(output)) return BadRequest();
|
if (string.IsNullOrEmpty(output)) return BadRequest();
|
||||||
return Ok(output);
|
return Ok(output);
|
||||||
}
|
}
|
||||||
|
@ -188,12 +113,14 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
|
||||||
{
|
{
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
var sshClient = serviceProvider.GetService<SshService>();
|
||||||
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
||||||
var server = serverConfigs.Find(x =>x.Id == serverId);
|
var server = serverConfigs.Find(x => x.Id == serverId);
|
||||||
if (server == null) return BadRequest();
|
if (server == null) return BadRequest();
|
||||||
var output = await sshClient?.ExecuteCommandAsync(serverId,$"echo {server.Password}","|","sudo -S /usr/sbin/fdisk -l","|","grep 'Disk /'","|","awk '{print $2,$3}'")!;
|
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);
|
if (string.IsNullOrEmpty(output)) return BadRequest(output);
|
||||||
var diskList = output.Split("\n", StringSplitOptions.RemoveEmptyEntries);
|
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();
|
var outList = diskList.Select(disk => disk.Split(":", StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
.Select(info => new { name = info[0], size = info[1] }).ToList();
|
||||||
return Ok(outList);
|
return Ok(outList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,45 +129,49 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
|
||||||
{
|
{
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
var sshClient = serviceProvider.GetService<SshService>();
|
||||||
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
||||||
var server = serverConfigs.Find(x =>x.Id == serverId);
|
var server = serverConfigs.Find(x => x.Id == serverId);
|
||||||
if (server == null) return BadRequest();
|
if (server == null) return BadRequest();
|
||||||
var output = await sshClient?.ExecuteCommandAsync(serverId,"ip link show" ,"|","grep '^[0-9]'","|","awk -F': ' '{print $2}'")!;
|
var output = await sshClient?.ExecuteCommandAsync(serverId, "ip link show", "|", "grep '^[0-9]'", "|",
|
||||||
|
"awk -F': ' '{print $2}'")!;
|
||||||
if (string.IsNullOrEmpty(output)) return BadRequest();
|
if (string.IsNullOrEmpty(output)) return BadRequest();
|
||||||
var data = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList();
|
var data = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList();
|
||||||
return Ok(data);
|
return Ok(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("GetServerNetworkEquipmentInfo")]
|
[HttpGet("GetServerNetworkEquipmentInfo")]
|
||||||
public async Task<IActionResult> GetServerNetworkEquipmentInfo([FromQuery] string serverId,[FromQuery] string networkId)
|
public async Task<IActionResult> GetServerNetworkEquipmentInfo([FromQuery] string serverId,
|
||||||
|
[FromQuery] string networkId)
|
||||||
{
|
{
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
var sshClient = serviceProvider.GetService<SshService>();
|
||||||
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
||||||
var server = serverConfigs.Find(x =>x.Id == serverId);
|
var server = serverConfigs.Find(x => x.Id == serverId);
|
||||||
if (server == null) return BadRequest();
|
if (server == null) return BadRequest();
|
||||||
var output = await sshClient?.ExecuteCommandAsync(serverId,"/usr/sbin/ifconfig",networkId)!;
|
var output = await sshClient?.ExecuteCommandAsync(serverId, "/usr/sbin/ifconfig", networkId)!;
|
||||||
if (string.IsNullOrEmpty(output)) return BadRequest();
|
if (string.IsNullOrEmpty(output)) return BadRequest();
|
||||||
output = string.Join(" ", output.Split("\n", StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()));
|
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();
|
var data = output.Split(" ", StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).Skip(1).ToList();
|
||||||
return Ok(data);
|
return Ok(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("GetServerGpuList")]
|
[HttpGet("GetServerGpuList")]
|
||||||
public async Task<IActionResult> GetServerGpuList([FromQuery] string serverId)
|
public async Task<IActionResult> GetServerGpuList([FromQuery] string serverId)
|
||||||
{
|
{
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
var sshClient = serviceProvider.GetService<SshService>();
|
||||||
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
||||||
var server = serverConfigs.Find(x =>x.Id == serverId);
|
var server = serverConfigs.Find(x => x.Id == serverId);
|
||||||
if (server == null) return BadRequest();
|
if (server == null) return BadRequest();
|
||||||
var output = await sshClient?.ExecuteCommandAsync(serverId,"lspci" ,"|","awk '/VGA/ {print $1}' ")!;
|
var output = await sshClient?.ExecuteCommandAsync(serverId, "lspci", "|", "awk '/VGA/ {print $1}' ")!;
|
||||||
if (string.IsNullOrEmpty(output)) return BadRequest();
|
if (string.IsNullOrEmpty(output)) return BadRequest();
|
||||||
var data = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList();
|
var data = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList();
|
||||||
return Ok(data);
|
return Ok(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("GetServerDiskInfo")]
|
[HttpGet("GetServerDiskInfo")]
|
||||||
public async Task<IActionResult> GetServerDiskInfo([FromQuery] string serverId,[FromQuery] string diskId)
|
public async Task<IActionResult> GetServerDiskInfo([FromQuery] string serverId, [FromQuery] string diskId)
|
||||||
{
|
{
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
var sshClient = serviceProvider.GetService<SshService>();
|
||||||
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
||||||
var server = serverConfigs.Find(x =>x.Id == serverId);
|
var server = serverConfigs.Find(x => x.Id == serverId);
|
||||||
if (server == null) return BadRequest();
|
if (server == null) return BadRequest();
|
||||||
var type = "ata";
|
var type = "ata";
|
||||||
if (diskId.StartsWith("nvme")) type = "nvme";
|
if (diskId.StartsWith("nvme")) type = "nvme";
|
||||||
|
@ -248,20 +179,20 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
|
||||||
else if (diskId.StartsWith("sd")) type = "ata";
|
else if (diskId.StartsWith("sd")) type = "ata";
|
||||||
else if (diskId.StartsWith("hd")) type = "ata";
|
else if (diskId.StartsWith("hd")) type = "ata";
|
||||||
diskId = $"/dev/{diskId}";
|
diskId = $"/dev/{diskId}";
|
||||||
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'")!;
|
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 (string.IsNullOrEmpty(output)) return BadRequest(output);
|
||||||
if (output.Contains("=== START OF INFORMATION SECTION ==="))
|
if (output.Contains("=== START OF INFORMATION SECTION ==="))
|
||||||
{
|
|
||||||
//截断
|
//截断
|
||||||
output = output.Substring(output.IndexOf("=== START OF INFORMATION SECTION ===", StringComparison.Ordinal) + "=== START OF INFORMATION SECTION ===".Length);
|
output = output.Substring(output.IndexOf("=== START OF INFORMATION SECTION ===", StringComparison.Ordinal) +
|
||||||
}
|
"=== START OF INFORMATION SECTION ===".Length);
|
||||||
var diskInfo = output
|
var diskInfo = output
|
||||||
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||||||
.Select(line => line.Trim().Split(':'))
|
.Select(line => line.Trim().Split(':'))
|
||||||
.ToList().Select(x => new
|
.ToList().Select(x => new
|
||||||
{
|
{
|
||||||
name=x[0].Replace(" ","_"),
|
name = x[0].Replace(" ", "_"),
|
||||||
value=x[1]
|
value = x[1]
|
||||||
}).ToList();
|
}).ToList();
|
||||||
return Ok(diskInfo);
|
return Ok(diskInfo);
|
||||||
}
|
}
|
||||||
|
@ -271,14 +202,18 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
|
||||||
{
|
{
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
var sshClient = serviceProvider.GetService<SshService>();
|
||||||
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
||||||
var server = serverConfigs.Find(x =>x.Id == serverId);
|
var server = serverConfigs.Find(x => x.Id == serverId);
|
||||||
if (server == null) return BadRequest();
|
if (server == null) return BadRequest();
|
||||||
var output = await sshClient?.ExecuteCommandAsync(serverId,"awk -F: '$1 != \"nobody\" &&$1 != \"build\" {print $1 \":\"$3}' /etc/passwd | sort -t: -k2nr")!;
|
var output = await sshClient?.ExecuteCommandAsync(serverId,
|
||||||
|
"awk -F: '$1 != \"nobody\" &&$1 != \"build\" {print $1 \":\"$3}' /etc/passwd | sort -t: -k2nr")!;
|
||||||
if (string.IsNullOrEmpty(output)) return BadRequest("无法获得用户树");
|
if (string.IsNullOrEmpty(output)) return BadRequest("无法获得用户树");
|
||||||
var data = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList();
|
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")!;
|
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();
|
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))}}' ")!;
|
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("无法获得用户登录记录");
|
if (string.IsNullOrEmpty(output)) return BadRequest("无法获得用户登录记录");
|
||||||
var loginList = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList();
|
var loginList = output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList();
|
||||||
var serverUserList = new List<ServerUserInfo>();
|
var serverUserList = new List<ServerUserInfo>();
|
||||||
|
@ -309,9 +244,9 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
|
||||||
var len = x.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();
|
var len = x.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||||
var name = len[0];
|
var name = len[0];
|
||||||
var d = serverUserList.Find(y => y.Name == name);
|
var d = serverUserList.Find(y => y.Name == name);
|
||||||
if(d==null) return;
|
if (d == null) return;
|
||||||
var port = len[1];
|
var port = len[1];
|
||||||
var time = string.Join(" ",len.Skip(2).ToList());
|
var time = string.Join(" ", len.Skip(2).ToList());
|
||||||
if (time != "NULL")
|
if (time != "NULL")
|
||||||
{
|
{
|
||||||
DateTimeOffset.TryParseExact(time,
|
DateTimeOffset.TryParseExact(time,
|
||||||
|
@ -321,51 +256,55 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
|
||||||
out var dateTimeOffset);
|
out var dateTimeOffset);
|
||||||
time = dateTimeOffset.ToString("G");
|
time = dateTimeOffset.ToString("G");
|
||||||
}
|
}
|
||||||
|
|
||||||
d.LastLoginTime = time;
|
d.LastLoginTime = time;
|
||||||
d.Port = port;
|
d.Port = port;
|
||||||
});
|
});
|
||||||
serverUserList = serverUserList.OrderByDescending(x => x.IsOnline).ThenByDescending(x => x.LastLoginTime).ToList();
|
serverUserList = serverUserList.OrderByDescending(x => x.IsOnline).ThenByDescending(x => x.LastLoginTime)
|
||||||
|
.ToList();
|
||||||
return Ok(serverUserList);
|
return Ok(serverUserList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpGet("GetServerProcessesList")]
|
[HttpGet("GetServerProcessesList")]
|
||||||
public async Task<IActionResult> GetServerProcessesList([FromQuery] string serverId,[FromQuery] string? userName)
|
public async Task<IActionResult> GetServerProcessesList([FromQuery] string serverId, [FromQuery] string? userName)
|
||||||
{
|
{
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
var sshClient = serviceProvider.GetService<SshService>();
|
||||||
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
||||||
var server = serverConfigs.Find(x => x.Id == serverId);
|
var server = serverConfigs.Find(x => x.Id == serverId);
|
||||||
if (server == null) return BadRequest();
|
if (server == null) return BadRequest();
|
||||||
var output = await sshClient?.ExecuteCommandAsync(serverId,
|
var output = await sshClient?.ExecuteCommandAsync(serverId,
|
||||||
"ps -eo pid,user,%cpu,%mem,comm --sort=-%cpu | awk 'NR>1'",userName!=null?$"| grep {userName}":"" )!;
|
"ps -eo pid,user,%cpu,%mem,comm --sort=-%cpu | awk 'NR>1'", userName != null ? $"| grep {userName}" : "")!;
|
||||||
var data = output.Split("\n", StringSplitOptions.RemoveEmptyEntries);
|
var data = output.Split("\n", StringSplitOptions.RemoveEmptyEntries);
|
||||||
var processList = data.Select(x =>
|
var processList = data.Select(x =>
|
||||||
{
|
{
|
||||||
x=x.Trim();
|
x = x.Trim();
|
||||||
var line = x.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();
|
var line = x.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||||
return new
|
return new
|
||||||
{
|
{
|
||||||
Pid=line[0],
|
Pid = line[0],
|
||||||
User = line[1],
|
User = line[1],
|
||||||
Cpu=line[2],
|
Cpu = line[2],
|
||||||
Memory=line[3],
|
Memory = line[3],
|
||||||
ProcessName=string.Join(" ",line.Skip(4))
|
ProcessName = string.Join(" ", line.Skip(4))
|
||||||
};
|
};
|
||||||
}).ToList();
|
}).ToList();
|
||||||
return Ok(processList);
|
return Ok(processList);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("GetServerProcessesKill")]
|
[HttpGet("GetServerProcessesKill")]
|
||||||
public async Task<IActionResult> GetServerProcessesKill([FromQuery] string serverId, [FromQuery] string pid,[FromQuery] bool force=false)
|
public async Task<IActionResult> GetServerProcessesKill([FromQuery] string serverId, [FromQuery] string pid,
|
||||||
|
[FromQuery] bool force = false)
|
||||||
{
|
{
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
var sshClient = serviceProvider.GetService<SshService>();
|
||||||
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
var serverConfigs = JobConfigHelper.GetServers().ToList();
|
||||||
var server = serverConfigs.Find(x => x.Id == serverId);
|
var server = serverConfigs.Find(x => x.Id == serverId);
|
||||||
if (server == null) return BadRequest();
|
if (server == null) return BadRequest();
|
||||||
var output = await sshClient?.ExecuteCommandAsync(serverId,
|
var output = await sshClient?.ExecuteCommandAsync(serverId,
|
||||||
$"echo {server.Password}","|","sudo -SS","kill",force?"-9":"-15",pid )!;
|
$"echo {server.Password}", "|", "sudo -SS", "kill", force ? "-9" : "-15", pid)!;
|
||||||
return Ok($"关闭信号已发送,{output}");
|
return Ok($"关闭信号已发送,{output}");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("GetServerNetworkList")]
|
[HttpGet("GetServerNetworkList")]
|
||||||
public async Task<IActionResult> GetServerNetworkList([FromQuery] string serverId, [FromQuery] string? userName)
|
public async Task<IActionResult> GetServerNetworkList([FromQuery] string serverId, [FromQuery] string? userName)
|
||||||
{
|
{
|
||||||
|
@ -374,7 +313,7 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
|
||||||
var server = serverConfigs.Find(x => x.Id == serverId);
|
var server = serverConfigs.Find(x => x.Id == serverId);
|
||||||
if (server == null) return BadRequest();
|
if (server == null) return BadRequest();
|
||||||
var output = await sshClient?.ExecuteCommandAsync(serverId,
|
var output = await sshClient?.ExecuteCommandAsync(serverId,
|
||||||
$"echo {server.Password}","|","sudo -S ss -tunapo")!;
|
$"echo {server.Password}", "|", "sudo -S ss -tunapo")!;
|
||||||
if (string.IsNullOrEmpty(output)) return BadRequest("返回为空");
|
if (string.IsNullOrEmpty(output)) return BadRequest("返回为空");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -386,15 +325,15 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
|
||||||
|
|
||||||
var networkList = data.Select(x => new
|
var networkList = data.Select(x => new
|
||||||
{
|
{
|
||||||
netId=x[0],
|
netId = x[0],
|
||||||
recvQ=x[2],
|
recvQ = x[2],
|
||||||
sendQ=x[3],
|
sendQ = x[3],
|
||||||
addressForm=x[4],
|
addressForm = x[4],
|
||||||
addressTo=x[5],
|
addressTo = x[5],
|
||||||
process=x[6].Split("),(",StringSplitOptions.RemoveEmptyEntries).Select(s=>new
|
process = x[6].Split("),(", StringSplitOptions.RemoveEmptyEntries).Select(s => new
|
||||||
{
|
{
|
||||||
name=s.Split(",")[0].Replace("users:((\"","").Replace("\"",""),
|
name = s.Split(",")[0].Replace("users:((\"", "").Replace("\"", ""),
|
||||||
pid=s.Split(",")[1].Split("=",StringSplitOptions.RemoveEmptyEntries)[1]
|
pid = s.Split(",")[1].Split("=", StringSplitOptions.RemoveEmptyEntries)[1]
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
return Ok(networkList);
|
return Ok(networkList);
|
||||||
|
@ -407,35 +346,35 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
|
||||||
|
|
||||||
[HttpPost("UpLoadWord")]
|
[HttpPost("UpLoadWord")]
|
||||||
public async Task<IActionResult> UpLoadWord(
|
public async Task<IActionResult> UpLoadWord(
|
||||||
[FromQuery] string serverId,
|
[FromQuery] string serverId,
|
||||||
[FromQuery] string userName,
|
[FromQuery] string userName,
|
||||||
[FromQuery] string wordId,
|
[FromQuery] string wordId,
|
||||||
[FromBody] WordModel content)
|
[FromBody] WordModel content)
|
||||||
{
|
{
|
||||||
// 拼接路径 markdowns/ServerId/id.json
|
// 拼接路径 markdowns/ServerId/id.json
|
||||||
var path = Path.Combine(AppContext.BaseDirectory,"markdowns",serverId, $"{wordId}.json");
|
var path = Path.Combine(AppContext.BaseDirectory, "markdowns", serverId, $"{wordId}.json");
|
||||||
var createAt=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
var createAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||||
var lastModifyAt = 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);
|
var directoryPath = Path.GetDirectoryName(path);
|
||||||
|
|
||||||
if (!Directory.Exists(directoryPath))
|
if (!Directory.Exists(directoryPath))
|
||||||
{
|
|
||||||
// 不存在则创建目录
|
// 不存在则创建目录
|
||||||
if (directoryPath != null) Directory.CreateDirectory(directoryPath);
|
if (directoryPath != null)
|
||||||
}
|
Directory.CreateDirectory(directoryPath);
|
||||||
|
|
||||||
// 检查文件是否存在
|
// 检查文件是否存在
|
||||||
if (System.IO.File.Exists(path))
|
if (System.IO.File.Exists(path))
|
||||||
{
|
{
|
||||||
// 读取现有JSON文件
|
// 读取现有JSON文件
|
||||||
var json = await System.IO.File.ReadAllTextAsync(path);
|
var json = await System.IO.File.ReadAllTextAsync(path);
|
||||||
// 反序列化JSON为动态对象
|
// 反序列化JSON为动态对象
|
||||||
dynamic existingWord = Newtonsoft.Json.JsonConvert.DeserializeObject(json) ?? new ExpandoObject();;
|
dynamic existingWord = JsonConvert.DeserializeObject(json) ?? new ExpandoObject();
|
||||||
|
;
|
||||||
|
|
||||||
// 使用内部的createAt更新createAt
|
// 使用内部的createAt更新createAt
|
||||||
createAt = existingWord.createAt;
|
createAt = existingWord.createAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 创建新的配置对象
|
// 创建新的配置对象
|
||||||
var newWord = new WordFileModel
|
var newWord = new WordFileModel
|
||||||
|
@ -446,9 +385,9 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
|
||||||
WordName = content.Name,
|
WordName = content.Name,
|
||||||
CreateAt = createAt,
|
CreateAt = createAt,
|
||||||
LastModifyAt = lastModifyAt
|
LastModifyAt = lastModifyAt
|
||||||
};
|
};
|
||||||
// 将新的配置对象序列化为JSON
|
// 将新的配置对象序列化为JSON
|
||||||
var newJson = Newtonsoft.Json.JsonConvert.SerializeObject(newWord);
|
var newJson = JsonConvert.SerializeObject(newWord);
|
||||||
|
|
||||||
// 覆盖写入新的JSON配置
|
// 覆盖写入新的JSON配置
|
||||||
await System.IO.File.WriteAllTextAsync(path, newJson);
|
await System.IO.File.WriteAllTextAsync(path, newJson);
|
||||||
|
@ -461,18 +400,15 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
|
||||||
public async Task<IActionResult> GetWordList([FromQuery] string serverId)
|
public async Task<IActionResult> GetWordList([FromQuery] string serverId)
|
||||||
{
|
{
|
||||||
var path = Path.Combine(AppContext.BaseDirectory, "markdowns", serverId);
|
var path = Path.Combine(AppContext.BaseDirectory, "markdowns", serverId);
|
||||||
if (!Directory.Exists(path))
|
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
|
||||||
{
|
|
||||||
Directory.CreateDirectory(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
var files = Directory.GetFiles(path);
|
var files = Directory.GetFiles(path);
|
||||||
var wordList = new List<WordFileModel>();
|
var wordList = new List<WordFileModel>();
|
||||||
foreach (var file in files)
|
foreach (var file in files)
|
||||||
{
|
{
|
||||||
var fileInfo = new FileInfo(file);
|
var fileInfo = new FileInfo(file);
|
||||||
var json = await System.IO.File.ReadAllTextAsync(file);
|
var json = await System.IO.File.ReadAllTextAsync(file);
|
||||||
var word = Newtonsoft.Json.JsonConvert.DeserializeObject<WordFileModel>(json);
|
var word = JsonConvert.DeserializeObject<WordFileModel>(json);
|
||||||
//去除content
|
//去除content
|
||||||
if (word == null) continue;
|
if (word == null) continue;
|
||||||
word.Content = null;
|
word.Content = null;
|
||||||
|
@ -480,6 +416,7 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
|
||||||
word.FileSize = fileSize.ToString();
|
word.FileSize = fileSize.ToString();
|
||||||
wordList.Add(word);
|
wordList.Add(word);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(wordList);
|
return Ok(wordList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,19 +425,19 @@ public class ServerController(IServiceProvider serviceProvider, ApplicationDbCon
|
||||||
{
|
{
|
||||||
var path = Path.Combine(AppContext.BaseDirectory, "markdowns", serverId, wordId + ".json");
|
var path = Path.Combine(AppContext.BaseDirectory, "markdowns", serverId, wordId + ".json");
|
||||||
var json = await System.IO.File.ReadAllTextAsync(path);
|
var json = await System.IO.File.ReadAllTextAsync(path);
|
||||||
var word = Newtonsoft.Json.JsonConvert.DeserializeObject<WordFileModel>(json);
|
var word = JsonConvert.DeserializeObject<WordFileModel>(json);
|
||||||
return Ok(word);
|
return Ok(word);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("GetWordTemplates")]
|
[HttpGet("GetWordTemplates")]
|
||||||
public async Task<IActionResult> GetWordTemplates()
|
public async Task<IActionResult> GetWordTemplates()
|
||||||
{
|
{
|
||||||
var path = Path.Combine(AppContext.BaseDirectory, "markdowns", "templates");
|
var path = Path.Combine(AppContext.BaseDirectory, "markdowns", "templates");
|
||||||
var files = Directory.GetFiles(path);
|
var files = Directory.GetFiles(path);
|
||||||
var templates = files.Select( x => new
|
var templates = files.Select(x => new
|
||||||
{
|
{
|
||||||
name = Path.GetFileName(x).Replace(".md",""),
|
name = Path.GetFileName(x).Replace(".md", ""),
|
||||||
content= System.IO.File.ReadAllTextAsync(x).Result
|
content = System.IO.File.ReadAllTextAsync(x).Result
|
||||||
}).ToList();
|
}).ToList();
|
||||||
return Ok(templates);
|
return Ok(templates);
|
||||||
}
|
}
|
||||||
|
@ -525,15 +462,15 @@ public class WordFileModel
|
||||||
public required string? Content { get; set; }
|
public required string? Content { get; set; }
|
||||||
public string? FileSize { get; set; }
|
public string? FileSize { get; set; }
|
||||||
public required string CreateAt { get; set; }
|
public required string CreateAt { get; set; }
|
||||||
public required string WordName { get; set;}
|
public required string WordName { get; set; }
|
||||||
public required string LastModifyAt { get; set; }
|
public required string LastModifyAt { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ServerUserInfo
|
public class ServerUserInfo
|
||||||
{
|
{
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
public required bool IsOnline { get; set; }
|
public required bool IsOnline { get; set; }
|
||||||
public required string? LastLoginTime { get; set; }
|
public required string? LastLoginTime { get; set; }
|
||||||
public required string? Address { get; set; }
|
public required string? Address { get; set; }
|
||||||
public required string? Port { get; set; }
|
public required string? Port { get; set; }
|
||||||
}
|
}
|
|
@ -19,7 +19,7 @@ public class UserController(UserManager<ApplicationUser> userManager) : Controll
|
||||||
if (user == null) return BadRequest("用户不存在");
|
if (user == null) return BadRequest("用户不存在");
|
||||||
//获取role
|
//获取role
|
||||||
var roles = await userManager.GetRolesAsync(user);
|
var roles = await userManager.GetRolesAsync(user);
|
||||||
|
|
||||||
// 创建一个匿名对象,只包含您想要公开的信息
|
// 创建一个匿名对象,只包含您想要公开的信息
|
||||||
var userInfo = new
|
var userInfo = new
|
||||||
{
|
{
|
||||||
|
@ -36,7 +36,7 @@ public class UserController(UserManager<ApplicationUser> userManager) : Controll
|
||||||
user.Avatar,
|
user.Avatar,
|
||||||
user.Desc,
|
user.Desc,
|
||||||
user.Posts,
|
user.Posts,
|
||||||
Role = roles[0],
|
Role = roles[0]
|
||||||
};
|
};
|
||||||
|
|
||||||
// 返回用户信息
|
// 返回用户信息
|
||||||
|
@ -78,7 +78,7 @@ public class UserController(UserManager<ApplicationUser> userManager) : Controll
|
||||||
{
|
{
|
||||||
user.Address,
|
user.Address,
|
||||||
user.Avatar,
|
user.Avatar,
|
||||||
CreateDate=user.CreateDate.ToLocalTime().ToString("U"),
|
CreateDate = user.CreateDate.ToLocalTime().ToString("U"),
|
||||||
user.Desc,
|
user.Desc,
|
||||||
user.Email,
|
user.Email,
|
||||||
user.Id,
|
user.Id,
|
||||||
|
@ -86,10 +86,10 @@ public class UserController(UserManager<ApplicationUser> userManager) : Controll
|
||||||
user.PhoneNumber,
|
user.PhoneNumber,
|
||||||
LastLoginTime = user.LastLoginTime?.ToLocalTime().ToString("U"),
|
LastLoginTime = user.LastLoginTime?.ToLocalTime().ToString("U"),
|
||||||
user.Posts,
|
user.Posts,
|
||||||
ModifiedDate=user.ModifiedDate.ToLocalTime().ToString("U"),
|
ModifiedDate = user.ModifiedDate.ToLocalTime().ToString("U"),
|
||||||
user.UserName,
|
user.UserName,
|
||||||
user.PhysicalAddress,
|
user.PhysicalAddress,
|
||||||
IsLock=user.LockoutEnd > DateTimeOffset.UtcNow
|
IsLock = user.LockoutEnd > DateTimeOffset.UtcNow
|
||||||
}).ToList();
|
}).ToList();
|
||||||
return Ok(userInfoList);
|
return Ok(userInfoList);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,63 +1,45 @@
|
||||||
using LoongPanel_Asp.Hubs;
|
namespace LoongPanel_Asp.Helpers;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
|
|
||||||
namespace LoongPanel_Asp.Helpers;
|
public static class DataHelper
|
||||||
|
|
||||||
public class DataHelper(ApplicationDbContext dbContext,IHubContext<SessionHub> context)
|
|
||||||
{
|
{
|
||||||
public async Task SaveData(ServerMonitoringData data)
|
public static void AddMonitoringData(List<ServerMonitoringData> res, string data, string dataName, string dataType)
|
||||||
{
|
{
|
||||||
// 保存数据到数据库
|
res.Add(new ServerMonitoringData
|
||||||
var dataDb = dbContext.ServerMonitoringData;
|
|
||||||
//获取当前时间
|
|
||||||
var time = DateTime.UtcNow;
|
|
||||||
data.Time = time;
|
|
||||||
dataDb.Add(data);
|
|
||||||
//提交
|
|
||||||
await dbContext.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
//批量添加
|
|
||||||
public async Task SaveData(List<ServerMonitoringData> data)
|
|
||||||
{
|
|
||||||
var dataDb = dbContext.ServerMonitoringData;
|
|
||||||
var time = DateTime.UtcNow;
|
|
||||||
foreach (var i in data)
|
|
||||||
{
|
{
|
||||||
i.Time = time;
|
ServerId = null,
|
||||||
dataDb.Add(i);
|
Data = data,
|
||||||
}
|
DataName = dataName,
|
||||||
|
DataType = dataType
|
||||||
await dbContext.SaveChangesAsync();
|
});
|
||||||
}
|
|
||||||
public async Task CheckData(string serverId,string valueType,string value,string valueName)
|
|
||||||
{
|
|
||||||
|
|
||||||
var alertConfigs = JobConfigHelper.GetAlerts();
|
|
||||||
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);
|
|
||||||
var key=$"{serverId}_{valueType}+{item}";
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// public async Task CheckData(string serverId,string valueType,string value,string valueName)
|
||||||
|
// {
|
||||||
|
//
|
||||||
|
// var alertConfigs = JobConfigHelper.GetAlerts();
|
||||||
|
// 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);
|
||||||
|
// var key=$"{serverId}_{valueType}+{item}";
|
||||||
|
// }
|
||||||
|
// 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);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
|
@ -33,7 +33,7 @@ public static class JobConfigHelper
|
||||||
Description = section.Keys["Description"],
|
Description = section.Keys["Description"],
|
||||||
CronExpression = section.Keys["CronExpression"],
|
CronExpression = section.Keys["CronExpression"],
|
||||||
ValueName = section.Keys["ValueName"],
|
ValueName = section.Keys["ValueName"],
|
||||||
Executor = section.Keys["Executor"].Split(',').Select(executor =>
|
Executor = section.Keys["Executor"]?.Split(',').Select(executor =>
|
||||||
{
|
{
|
||||||
// 查找serverConfigs 中id匹配 未找到则跳过
|
// 查找serverConfigs 中id匹配 未找到则跳过
|
||||||
var server = serverConfigs.FirstOrDefault(server => server.Id == executor);
|
var server = serverConfigs.FirstOrDefault(server => server.Id == executor);
|
||||||
|
@ -71,7 +71,7 @@ public static class JobConfigHelper
|
||||||
return _serverConfigs;
|
return _serverConfigs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Dictionary<string,AlertConfiguration> GetAlerts()
|
public static Dictionary<string, AlertConfiguration> GetAlerts()
|
||||||
{
|
{
|
||||||
if (_alertsConfigs != null) return _alertsConfigs;
|
if (_alertsConfigs != null) return _alertsConfigs;
|
||||||
|
|
||||||
|
@ -81,11 +81,11 @@ public static class JobConfigHelper
|
||||||
var alertsFolderPath = Path.Combine(Environment.CurrentDirectory, "Configs", "Alerts");
|
var alertsFolderPath = Path.Combine(Environment.CurrentDirectory, "Configs", "Alerts");
|
||||||
// 检查目录是否存在,如果不存在则创建
|
// 检查目录是否存在,如果不存在则创建
|
||||||
if (!Directory.Exists(alertsFolderPath)) Directory.CreateDirectory(alertsFolderPath);
|
if (!Directory.Exists(alertsFolderPath)) Directory.CreateDirectory(alertsFolderPath);
|
||||||
|
|
||||||
// 获取目录下所有.ini文件的路径
|
// 获取目录下所有.ini文件的路径
|
||||||
var alertFiles = Directory.GetFiles(alertsFolderPath, "*.ini");
|
var alertFiles = Directory.GetFiles(alertsFolderPath, "*.ini");
|
||||||
var notifyUsersMap = new Dictionary<string,Dictionary<string, Dictionary<string, List<string>>>>();
|
var notifyUsersMap = new Dictionary<string, Dictionary<string, Dictionary<string, List<string>>>>();
|
||||||
var warningUsersMap = new Dictionary<string,Dictionary<string, Dictionary<string, List<string>>>>();
|
var warningUsersMap = new Dictionary<string, Dictionary<string, Dictionary<string, List<string>>>>();
|
||||||
// 读取配置信息
|
// 读取配置信息
|
||||||
foreach (var alertFile in alertFiles)
|
foreach (var alertFile in alertFiles)
|
||||||
{
|
{
|
||||||
|
@ -97,7 +97,7 @@ public static class JobConfigHelper
|
||||||
{
|
{
|
||||||
var serverId = alertSection.SectionName.Split("_")[1];
|
var serverId = alertSection.SectionName.Split("_")[1];
|
||||||
var type = alertSection.SectionName.Split("_")[0];
|
var type = alertSection.SectionName.Split("_")[0];
|
||||||
var notifyValue= alertSection.Keys["Notify"];
|
var notifyValue = alertSection.Keys["Notify"];
|
||||||
var warningValue = alertSection.Keys["Warning"];
|
var warningValue = alertSection.Keys["Warning"];
|
||||||
var userId = Path.GetFileNameWithoutExtension(alertFile);
|
var userId = Path.GetFileNameWithoutExtension(alertFile);
|
||||||
if (!notifyUsersMap.TryGetValue(serverId, out var notifyTypes))
|
if (!notifyUsersMap.TryGetValue(serverId, out var notifyTypes))
|
||||||
|
@ -111,6 +111,7 @@ public static class JobConfigHelper
|
||||||
warningTypes = [];
|
warningTypes = [];
|
||||||
warningUsersMap.Add(serverId, warningTypes);
|
warningUsersMap.Add(serverId, warningTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!notifyTypes.TryGetValue(type, out var notifyValues))
|
if (!notifyTypes.TryGetValue(type, out var notifyValues))
|
||||||
{
|
{
|
||||||
notifyValues = [];
|
notifyValues = [];
|
||||||
|
@ -128,25 +129,30 @@ public static class JobConfigHelper
|
||||||
notifyUsersList = [];
|
notifyUsersList = [];
|
||||||
notifyValues.Add(notifyValue, notifyUsersList);
|
notifyValues.Add(notifyValue, notifyUsersList);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!warningValues.TryGetValue(warningValue, out var warningUsersList))
|
if (!warningValues.TryGetValue(warningValue, out var warningUsersList))
|
||||||
{
|
{
|
||||||
warningUsersList = [];
|
warningUsersList = [];
|
||||||
warningValues.Add(warningValue, warningUsersList);
|
warningValues.Add(warningValue, warningUsersList);
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyUsersList.Add(userId);
|
notifyUsersList.Add(userId);
|
||||||
warningUsersList.Add(userId);
|
warningUsersList.Add(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//遍历 notifyUsersMap
|
//遍历 notifyUsersMap
|
||||||
foreach (var (serverId, notifyUsersList) in notifyUsersMap)
|
foreach (var (serverId, notifyUsersList) in notifyUsersMap)
|
||||||
{
|
{
|
||||||
//获得key 和 value
|
//获得key 和 value
|
||||||
if(!alertsConfigs.TryGetValue(serverId, out var alertsConfig)){
|
if (!alertsConfigs.TryGetValue(serverId, out var alertsConfig))
|
||||||
|
{
|
||||||
//创建新的
|
//创建新的
|
||||||
alertsConfig = new AlertConfiguration();
|
alertsConfig = new AlertConfiguration();
|
||||||
alertsConfigs.Add(serverId, alertsConfig);
|
alertsConfigs.Add(serverId, alertsConfig);
|
||||||
}
|
}
|
||||||
alertsConfig.Notify= notifyUsersList;
|
|
||||||
|
alertsConfig.Notify = notifyUsersList;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (serverId, emailUsersList) in warningUsersMap)
|
foreach (var (serverId, emailUsersList) in warningUsersMap)
|
||||||
|
@ -158,19 +164,20 @@ public static class JobConfigHelper
|
||||||
alertsConfig = new AlertConfiguration();
|
alertsConfig = new AlertConfiguration();
|
||||||
alertsConfigs.Add(serverId, alertsConfig);
|
alertsConfigs.Add(serverId, alertsConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
alertsConfig.Warning = emailUsersList;
|
alertsConfig.Warning = emailUsersList;
|
||||||
}
|
}
|
||||||
|
|
||||||
_alertsConfigs =alertsConfigs;
|
_alertsConfigs = alertsConfigs;
|
||||||
return alertsConfigs;
|
return alertsConfigs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ReloadAlerts()
|
public static void ReloadAlerts()
|
||||||
{
|
{
|
||||||
_alertsConfigs = null;
|
_alertsConfigs = null;
|
||||||
GetAlerts();
|
GetAlerts();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ReloadServers()
|
public static void ReloadServers()
|
||||||
{
|
{
|
||||||
_serverConfigs = null;
|
_serverConfigs = null;
|
||||||
|
@ -190,7 +197,7 @@ public class JobConfiguration
|
||||||
public string? Description { get; init; }
|
public string? Description { get; init; }
|
||||||
|
|
||||||
//执行者
|
//执行者
|
||||||
public required List<ServerModel?> Executor { get; init; }
|
public List<ServerModel?>? Executor { get; init; }
|
||||||
|
|
||||||
//任务类型
|
//任务类型
|
||||||
public required string JobType { get; init; }
|
public required string JobType { get; init; }
|
||||||
|
@ -202,9 +209,10 @@ public class JobConfiguration
|
||||||
public string? ValueName { get; init; }
|
public string? ValueName { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AlertConfiguration(Dictionary<string, Dictionary<string, List<string>>>? notify=null,
|
public class AlertConfiguration(
|
||||||
Dictionary<string, Dictionary<string, List<string>>>? warning=null)
|
Dictionary<string, Dictionary<string, List<string>>>? notify = null,
|
||||||
|
Dictionary<string, Dictionary<string, List<string>>>? warning = null)
|
||||||
{
|
{
|
||||||
public Dictionary<string, Dictionary<string, List<string>>> Notify { get; set; } = notify ?? [];
|
public Dictionary<string, Dictionary<string, List<string>>> Notify { get; set; } = notify ?? [];
|
||||||
public Dictionary<string, Dictionary<string, List<string>>> Warning { get; set; } = warning ?? [];
|
public Dictionary<string, Dictionary<string, List<string>>> Warning { get; set; } = warning ?? [];
|
||||||
}
|
}
|
|
@ -17,7 +17,7 @@ public class SessionHub(UserManager<ApplicationUser> userManager, ILiteDatabase
|
||||||
// 查询获取用户对象
|
// 查询获取用户对象
|
||||||
var user = await userManager.FindByIdAsync(userId);
|
var user = await userManager.FindByIdAsync(userId);
|
||||||
user!.Address = ip?.ToString();
|
user!.Address = ip?.ToString();
|
||||||
|
|
||||||
user!.LastLoginTime = DateTime.UtcNow;
|
user!.LastLoginTime = DateTime.UtcNow;
|
||||||
// 更新用户信息
|
// 更新用户信息
|
||||||
await userManager.UpdateAsync(user);
|
await userManager.UpdateAsync(user);
|
||||||
|
@ -31,7 +31,7 @@ public class SessionHub(UserManager<ApplicationUser> userManager, ILiteDatabase
|
||||||
NickName = user.NickName!,
|
NickName = user.NickName!,
|
||||||
Avatar = user.Avatar,
|
Avatar = user.Avatar,
|
||||||
Role = role.ToList(),
|
Role = role.ToList(),
|
||||||
Posts = user.Posts,
|
Posts = user.Posts
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,12 +64,11 @@ public class SessionHub(UserManager<ApplicationUser> userManager, ILiteDatabase
|
||||||
await base.OnDisconnectedAsync(exception);
|
await base.OnDisconnectedAsync(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendMessage(string userId, string receiver ,string message)
|
public async Task SendMessage(string userId, string receiver, string message)
|
||||||
{
|
{
|
||||||
Console.WriteLine("12312312312{0},{1}", userId, message);
|
Console.WriteLine("12312312312{0},{1}", userId, message);
|
||||||
await Clients.Group(receiver).SendAsync("sendMessage", userId, message);
|
await Clients.Group(receiver).SendAsync("sendMessage", userId, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//定义类型UserInfo
|
//定义类型UserInfo
|
||||||
|
@ -82,6 +81,6 @@ public class UserInfo
|
||||||
public required string Avatar { get; init; }
|
public required string Avatar { get; init; }
|
||||||
|
|
||||||
public required List<string> Role { get; init; }
|
public required List<string> Role { get; init; }
|
||||||
|
|
||||||
public required string Posts { get; init;}
|
public required string Posts { get; init; }
|
||||||
}
|
}
|
|
@ -4,34 +4,35 @@ using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
namespace LoongPanel_Asp.Hubs;
|
namespace LoongPanel_Asp.Hubs;
|
||||||
|
|
||||||
public class TerminalHub(SshStreamService sshStreamService):Hub
|
public class TerminalHub(SshStreamService sshStreamService) : Hub
|
||||||
{
|
{
|
||||||
|
|
||||||
public override async Task OnConnectedAsync()
|
public override async Task OnConnectedAsync()
|
||||||
{
|
{
|
||||||
var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value;
|
var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value;
|
||||||
await Groups.AddToGroupAsync(Context.ConnectionId, userId);
|
await Groups.AddToGroupAsync(Context.ConnectionId, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
//create a terminal
|
//create a terminal
|
||||||
public void CreateTerminal(string serverId)
|
public void CreateTerminal(string serverId)
|
||||||
{
|
{
|
||||||
var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value;
|
var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value;
|
||||||
sshStreamService.Connect(userId, serverId);
|
sshStreamService.Connect(userId, serverId);
|
||||||
}
|
}
|
||||||
|
|
||||||
//send a message to the terminal
|
//send a message to the terminal
|
||||||
public void SendMessage(string message)
|
public void SendMessage(string message)
|
||||||
{
|
{
|
||||||
var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value;
|
var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value;
|
||||||
Console.WriteLine(message);
|
Console.WriteLine(message);
|
||||||
sshStreamService.Write(userId,message);
|
sshStreamService.Write(userId, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResizeTerminal(int row, int col)
|
public void ResizeTerminal(int row, int col)
|
||||||
{
|
{
|
||||||
var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value;
|
var userId = Context.User!.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value;
|
||||||
sshStreamService.ReSize(userId,col,row);
|
sshStreamService.ReSize(userId, col, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
//断开
|
//断开
|
||||||
public override Task OnDisconnectedAsync(Exception? exception)
|
public override Task OnDisconnectedAsync(Exception? exception)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,62 +1,52 @@
|
||||||
using Microsoft.AspNetCore.Identity;
|
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
|
namespace LoongPanel_Asp;
|
||||||
|
|
||||||
|
public class Init : IHostedService
|
||||||
{
|
{
|
||||||
public class Init : IHostedService
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public Init(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider _serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
public Init(IServiceProvider serviceProvider)
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using var scope = _serviceProvider.CreateScope();
|
||||||
|
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||||
|
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<ApplicationRole>>();
|
||||||
|
// 检查管理员用户是否存在
|
||||||
|
var adminUser = await userManager.FindByNameAsync("admin");
|
||||||
|
if (adminUser == null)
|
||||||
{
|
{
|
||||||
_serviceProvider = serviceProvider;
|
adminUser = new ApplicationUser
|
||||||
}
|
|
||||||
|
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
using var scope = _serviceProvider.CreateScope();
|
|
||||||
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
|
||||||
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<ApplicationRole>>();
|
|
||||||
// 检查管理员用户是否存在
|
|
||||||
var adminUser = await userManager.FindByNameAsync("admin");
|
|
||||||
if (adminUser == null)
|
|
||||||
{
|
{
|
||||||
adminUser = new ApplicationUser
|
Avatar = "https://api.multiavatar.com/admin.svg",
|
||||||
{
|
Posts = "管理员",
|
||||||
Avatar = "https://api.multiavatar.com/admin.svg",
|
CreateDate = DateTime.UtcNow,
|
||||||
Posts = "管理员",
|
ModifiedDate = DateTime.UtcNow,
|
||||||
CreateDate = DateTime.UtcNow,
|
Email = "admin@admin.com",
|
||||||
ModifiedDate = DateTime.UtcNow,
|
UserName = "admin",
|
||||||
Email = "admin@admin.com",
|
PhoneNumber = "999999999",
|
||||||
UserName = "admin",
|
NickName = "默认管理员"
|
||||||
PhoneNumber = "999999999",
|
};
|
||||||
NickName = "默认管理员",
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await userManager.CreateAsync(adminUser, "Qwertyuiop123!@#");
|
var result = await userManager.CreateAsync(adminUser, "Qwertyuiop123!@#");
|
||||||
//分配管理员角色
|
//分配管理员角色
|
||||||
if (result.Succeeded)
|
if (result.Succeeded)
|
||||||
await userManager.AddToRoleAsync(adminUser, "admin");
|
await userManager.AddToRoleAsync(adminUser, "admin");
|
||||||
if (result.Succeeded)
|
if (result.Succeeded)
|
||||||
Console.WriteLine("管理员创建成功,账号:{0},密码:{1}", "admin", "Qwertyuiop123!@#");
|
Console.WriteLine("管理员创建成功,账号:{0},密码:{1}", "admin", "Qwertyuiop123!@#");
|
||||||
else
|
else
|
||||||
foreach (var error in result.Errors)
|
foreach (var error in result.Errors)
|
||||||
{
|
Console.WriteLine(error.Description);
|
||||||
Console.WriteLine(error.Description);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
// 这里可以执行一些应用程序停止时的清理操作
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// 这里可以执行一些应用程序停止时的清理操作
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,183 +1,53 @@
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using LoongPanel_Asp.Helpers;
|
using LoongPanel_Asp.Helpers;
|
||||||
using LoongPanel_Asp.Hubs;
|
|
||||||
using LoongPanel_Asp.Models;
|
using LoongPanel_Asp.Models;
|
||||||
using LoongPanel_Asp.Servers;
|
using LoongPanel_Asp.Servers;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
using Quartz;
|
using Quartz;
|
||||||
using Property = LoongPanel_Asp.Models.Property;
|
|
||||||
|
|
||||||
namespace LoongPanel_Asp.Jobs;
|
namespace LoongPanel_Asp.Jobs;
|
||||||
|
|
||||||
public class CpuTotalJob(
|
public class CpuTotalJob(DataService dataService, SshService sshService) : IiJob(dataService, sshService)
|
||||||
IHubContext<SessionHub> hubContext,
|
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
ApplicationDbContext dbContext) : IJob
|
|
||||||
{
|
{
|
||||||
private static int _count;
|
protected override async Task ExecuteInternal(IJobExecutionContext context, ServerModel server,
|
||||||
|
SshService sshService, List<ServerMonitoringData> res)
|
||||||
public async Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
{
|
||||||
var dataMap = context.JobDetail.JobDataMap;
|
var output = await sshService.ExecuteCommandAsync(server.Id, "sar", "-u", "3 1", "|", "grep", "Average");
|
||||||
var dataHelper = new DataHelper(dbContext,hubContext);
|
if (string.IsNullOrEmpty(output)) return;
|
||||||
var serverList = (List<ServerModel>)dataMap["executor"];
|
var values = output.Split(' ', StringSplitOptions.RemoveEmptyEntries).Skip(2).ToList();
|
||||||
var cpuDataListAll = new List<ServerMonitoringData>();
|
DataHelper.AddMonitoringData(res, values[0], "CPU用户使用率", "CpuUserUsage");
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
DataHelper.AddMonitoringData(res, values[2], "CPU系统使用率", "CpuSystemUsage");
|
||||||
foreach (var server in serverList)
|
DataHelper.AddMonitoringData(res, values[3], "CPUIO等待率", "CpuIOWaitUsage");
|
||||||
{
|
DataHelper.AddMonitoringData(res, (100 - double.Parse(values[5])).ToString(CultureInfo.InvariantCulture),
|
||||||
var output = await sshClient?.ExecuteCommandAsync(server.Id, "sar", "-u", "3 1", "|", "grep", "Average")!;
|
"CPU总使用率", "CpuTotalUsage");
|
||||||
if (string.IsNullOrEmpty(output)) continue;
|
|
||||||
|
|
||||||
output = output.Replace("Average:", "").Replace("all", "").TrimStart();
|
|
||||||
var cpuList = output.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
|
|
||||||
var cpuProperties = new List<Property>
|
|
||||||
{
|
|
||||||
new() { Name = "CpuUserUsage", DisplayName = "CPU用户使用率", Order = 0 },
|
|
||||||
new() { Name = "CpuSystemUsage", DisplayName = "CPU系统使用率", Order = 1 },
|
|
||||||
new() { Name = "CpuIOWaitUsage", DisplayName = "CPUIO等待使用率", Order = 2 }
|
|
||||||
};
|
|
||||||
|
|
||||||
var cpuDataList = cpuProperties.Select(property => new ServerMonitoringData { ServerId = server.Id, Data = cpuList[(int)property.Order!], DataName = property.DisplayName, DataType = property.Name }).ToList();
|
|
||||||
|
|
||||||
// Calculate CpuTotalUsage separately
|
|
||||||
var idleUsage = double.Parse(cpuList[5]);
|
|
||||||
var totalUsage = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = (100 - idleUsage).ToString(CultureInfo.InvariantCulture),
|
|
||||||
DataName = "CPU总使用率",
|
|
||||||
DataType = "CpuTotalUsage"
|
|
||||||
};
|
|
||||||
cpuDataList.Add(totalUsage);
|
|
||||||
cpuDataList.ForEach(async 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
_count++;
|
|
||||||
if (_count <= 10) return;
|
|
||||||
_count = 0;
|
|
||||||
// Add to database
|
|
||||||
await dataHelper.SaveData(cpuDataListAll);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CpuSpeedJob(
|
public class CpuSpeedJob(DataService dataService, SshService sshService) : IiJob(dataService, sshService)
|
||||||
IHubContext<SessionHub> hubContext,
|
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
ApplicationDbContext dbContext) : IJob
|
|
||||||
{
|
{
|
||||||
private static int _count;
|
protected override async Task ExecuteInternal(IJobExecutionContext context, ServerModel server,
|
||||||
|
SshService sshService, List<ServerMonitoringData> res)
|
||||||
public async Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
{
|
||||||
var dataMap = context.JobDetail.JobDataMap;
|
var output = await sshService.ExecuteCommandAsync(server.Id, "cat", "/proc/cpuinfo", "|", "grep", "'cpu MHz'");
|
||||||
var dataHelper = new DataHelper(dbContext,hubContext);
|
if (string.IsNullOrEmpty(output)) return;
|
||||||
var serverList = (List<ServerModel>)dataMap["executor"];
|
var cpuSpeeds = output.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||||||
var cpuDataListAll = new List<ServerMonitoringData>();
|
.Select(line => line.Split(':', 2)[1].Trim())
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
.Where(speed => !string.IsNullOrEmpty(speed)).ToList();
|
||||||
foreach (var server in serverList)
|
DataHelper.AddMonitoringData(res, cpuSpeeds[0], "CPU总频率", "CpuTotalSpeed");
|
||||||
{
|
cpuSpeeds.Skip(1).Select((speed, i) => (speed, i)).ToList().ForEach(x =>
|
||||||
var output =
|
DataHelper.AddMonitoringData(res, x.speed, $"CPU{x.i + 1}频率", $"CpuSingleSpeed-{x.i + 1}"));
|
||||||
await sshClient?.ExecuteCommandAsync(server.Id, "cat", "/proc/cpuinfo", "|", "grep", "'cpu MHz'")!;
|
|
||||||
if (string.IsNullOrEmpty(output)) continue;
|
|
||||||
//切分每行
|
|
||||||
var cpuSpeedList = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
//获取第一行
|
|
||||||
var cpuTotalSpeed = cpuSpeedList[0].Split(':', StringSplitOptions.RemoveEmptyEntries)[1].Trim();
|
|
||||||
var cpuDataList = new List<ServerMonitoringData>();
|
|
||||||
var totalSpeed = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = cpuTotalSpeed,
|
|
||||||
DataName = "CPU总速度",
|
|
||||||
DataType = "CpuTotalSpeed"
|
|
||||||
};
|
|
||||||
cpuDataList.Add(totalSpeed);
|
|
||||||
//遍历剩下的行
|
|
||||||
foreach (var (cpuSpeed, index) in cpuSpeedList.Skip(1).Select((x, index) => (x, index)))
|
|
||||||
{
|
|
||||||
var speed = cpuSpeed.Split(':', StringSplitOptions.RemoveEmptyEntries)[1].Trim();
|
|
||||||
var singleSpeed = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = speed,
|
|
||||||
DataName = $"CPU单核速度-{index}",
|
|
||||||
DataType = $"CpuSingleSpeed-{index}"
|
|
||||||
};
|
|
||||||
cpuDataList.Add(singleSpeed);
|
|
||||||
}
|
|
||||||
|
|
||||||
cpuDataListAll.AddRange(cpuDataList);
|
|
||||||
|
|
||||||
cpuDataList.ForEach(Action);
|
|
||||||
continue;
|
|
||||||
|
|
||||||
async void Action(ServerMonitoringData data)
|
|
||||||
{
|
|
||||||
await hubContext.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data);
|
|
||||||
await dataHelper.CheckData(server.Id, data.DataType ?? "", data.Data ?? "", data.DataName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_count++;
|
|
||||||
if (_count <= 10) return;
|
|
||||||
_count = 0;
|
|
||||||
await dataHelper.SaveData(cpuDataListAll);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CpuSingleUsageJob(
|
public class CpuSingleUsageJob(DataService dataService, SshService sshService) : IiJob(dataService, sshService)
|
||||||
IHubContext<SessionHub> hubContext,
|
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
ApplicationDbContext dbContext) : IJob
|
|
||||||
{
|
{
|
||||||
private static int _count;
|
protected override async Task ExecuteInternal(IJobExecutionContext context, ServerModel server,
|
||||||
|
SshService sshService, List<ServerMonitoringData> res)
|
||||||
public async Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
{
|
||||||
var dataMap = context.JobDetail.JobDataMap;
|
var output = await sshService.ExecuteCommandAsync(server.Id, "sar -P ALL 3 1", "|", "grep Average", "|",
|
||||||
var dataHelper = new DataHelper(dbContext,hubContext);
|
"awk 'NR>2 {print 100-$NF}'");
|
||||||
var serverList = (List<ServerModel>)dataMap["executor"];
|
if (string.IsNullOrEmpty(output)) return;
|
||||||
var cpuDataListAll = new List<ServerMonitoringData>();
|
var values = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||||
foreach (var server in serverList)
|
values.Select((value, i) => (value, i)).ToList().ForEach(x =>
|
||||||
{
|
DataHelper.AddMonitoringData(res, x.value, $"CPU{x.i}使用率", $"CpuSingleUsage-{x.i}"));
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
|
||||||
var output =
|
|
||||||
await sshClient?.ExecuteCommandAsync(server.Id, "sar -P ALL 3 1", "|", "grep Average", "|",
|
|
||||||
"awk 'NR>2 {print 100-$NF}'")!;
|
|
||||||
if (string.IsNullOrEmpty(output)) continue;
|
|
||||||
var cpuDataList = new List<ServerMonitoringData>();
|
|
||||||
foreach (var (cpuUsage, index) in output.Split("\n", StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Select((x, index) => (x, index)))
|
|
||||||
{
|
|
||||||
var singleUsage = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = cpuUsage,
|
|
||||||
DataName = $"CPU单核使用率-{index}",
|
|
||||||
DataType = $"CpuSingleUsage-{index}"
|
|
||||||
};
|
|
||||||
cpuDataList.Add(singleUsage);
|
|
||||||
}
|
|
||||||
|
|
||||||
cpuDataList.ForEach(Action);
|
|
||||||
continue;
|
|
||||||
|
|
||||||
async void Action(ServerMonitoringData data)
|
|
||||||
{
|
|
||||||
await hubContext.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data);
|
|
||||||
await dataHelper.CheckData(server.Id, data.DataType ?? "", data.Data ?? "", data.DataName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_count++;
|
|
||||||
if (_count <= 10) return;
|
|
||||||
_count = 0;
|
|
||||||
await dataHelper.SaveData(cpuDataListAll);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,137 +1,39 @@
|
||||||
using LoongPanel_Asp.Helpers;
|
using LoongPanel_Asp.Helpers;
|
||||||
using LoongPanel_Asp.Hubs;
|
|
||||||
using LoongPanel_Asp.Models;
|
using LoongPanel_Asp.Models;
|
||||||
using LoongPanel_Asp.Servers;
|
using LoongPanel_Asp.Servers;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
|
||||||
namespace LoongPanel_Asp.Jobs;
|
namespace LoongPanel_Asp.Jobs;
|
||||||
|
|
||||||
public class DiskTotalJob(
|
public class DiskTotalJob(DataService dataService, SshService sshService) : IiJob(dataService, sshService)
|
||||||
IHubContext<SessionHub> hubContext,
|
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
ApplicationDbContext dbContext) : IJob
|
|
||||||
{
|
{
|
||||||
private static int _count;
|
protected override async Task ExecuteInternal(IJobExecutionContext context, ServerModel server,
|
||||||
|
SshService sshService, List<ServerMonitoringData> res)
|
||||||
public async Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
{
|
||||||
var dataMap = context.JobDetail.JobDataMap;
|
var output = await sshService.ExecuteCommandAsync(server.Id, "df", "--total", "|", "grep", "total");
|
||||||
var dataHelper = new DataHelper(dbContext,hubContext);
|
if (string.IsNullOrEmpty(output)) return;
|
||||||
var serverList = (List<ServerModel>)dataMap["executor"];
|
var diskTotal = output.Split(" ", StringSplitOptions.RemoveEmptyEntries)[4].Replace("%", "");
|
||||||
//获得cpu信息
|
DataHelper.AddMonitoringData(res, diskTotal, "磁盘总使用率", "DiskTotalUsage");
|
||||||
var diskDataListAll = new List<ServerMonitoringData>();
|
|
||||||
foreach (var server in serverList)
|
|
||||||
{
|
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
|
||||||
var diskDataList = new List<ServerMonitoringData>();
|
|
||||||
var output = await sshClient?.ExecuteCommandAsync(server.Id, "df", "--total", "|", "grep", "total")!;
|
|
||||||
if (string.IsNullOrEmpty(output)) return;
|
|
||||||
var disk = output.Split(" ", StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
var diskTotalUsage = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = disk[4].Replace("%", ""),
|
|
||||||
DataName = "磁盘总使用率",
|
|
||||||
DataType = "DiskTotalUsage"
|
|
||||||
};
|
|
||||||
await hubContext.Clients.All.SendAsync("ReceiveData", diskTotalUsage.ServerId, diskTotalUsage.DataType,
|
|
||||||
diskTotalUsage.Data);
|
|
||||||
await dataHelper.CheckData(server.Id, diskTotalUsage.DataType ?? "", diskTotalUsage.Data ?? "", "磁盘总使用率");
|
|
||||||
diskDataList.Add(diskTotalUsage);
|
|
||||||
diskDataListAll.AddRange(diskDataList);
|
|
||||||
}
|
|
||||||
|
|
||||||
_count++;
|
|
||||||
if (_count <= 10) return;
|
|
||||||
_count = 0;
|
|
||||||
await dataHelper.SaveData(diskDataListAll);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DiskUseJob(
|
public class DiskUseJob(DataService dataService, SshService sshService) : IiJob(dataService, sshService)
|
||||||
IHubContext<SessionHub> hubContext,
|
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
ApplicationDbContext dbContext) : IJob
|
|
||||||
{
|
{
|
||||||
private static int _count;
|
protected override async Task ExecuteInternal(IJobExecutionContext context, ServerModel server,
|
||||||
|
SshService sshService, List<ServerMonitoringData> res)
|
||||||
public async Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
{
|
||||||
var dataMap = context.JobDetail.JobDataMap;
|
var output = await sshService.ExecuteCommandAsync(server.Id,
|
||||||
var dataHelper = new DataHelper(dbContext,hubContext);
|
"sar -d 3 1 | grep Average: | awk 'NR>1' | awk '{$1=\"\";print$0}'");
|
||||||
var serverList = (List<ServerModel>)dataMap["executor"];
|
if (string.IsNullOrEmpty(output)) return;
|
||||||
//获得cpu信息
|
|
||||||
var diskDataListAll = new List<ServerMonitoringData>();
|
output.Split("\n").Select(x => x.Trim()).Where(line => !string.IsNullOrEmpty(line)).ToList().ForEach(line =>
|
||||||
foreach (var server in serverList)
|
|
||||||
{
|
{
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
var diskValues = line.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||||
var diskDataList = new List<ServerMonitoringData>();
|
var diskName = diskValues[0];
|
||||||
var output =
|
DataHelper.AddMonitoringData(res, diskValues[1], $"磁盘每秒传输数-{diskName}", $"DiskTps-{diskName}");
|
||||||
await sshClient?.ExecuteCommandAsync(server.Id, "sar -d 3 1", "|", "grep Average:", "|", "awk 'NR>1'","|","awk '{$1=\"\";print $0}'")!;
|
DataHelper.AddMonitoringData(res, diskValues[2], $"磁盘每秒读数-{diskName}", $"DiskReadKB-{diskName}");
|
||||||
if (string.IsNullOrEmpty(output)) return;
|
DataHelper.AddMonitoringData(res, diskValues[3], $"磁盘每秒写数-{diskName}", $"DiskWriteKB-{diskName}");
|
||||||
var lines = output.Split("\n", StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList();
|
DataHelper.AddMonitoringData(res, diskValues[8], $"磁盘利用率-{diskName}", $"DiskUtil-{diskName}");
|
||||||
|
});
|
||||||
foreach (var line in lines)
|
|
||||||
{
|
|
||||||
var disk = line.Split(" ", StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
var dev = disk[0];
|
|
||||||
// 每秒传输数
|
|
||||||
var diskTps = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = disk[1],
|
|
||||||
DataName = $"磁盘每秒传输数-{dev}" ,
|
|
||||||
DataType = $"DiskTps-{dev}"
|
|
||||||
};
|
|
||||||
diskDataList.Add(diskTps);
|
|
||||||
var diskReadKb = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = disk[2],
|
|
||||||
DataName = $"磁盘每秒读取数据量-{dev}" ,
|
|
||||||
DataType = $"DiskReadKB-{dev}"
|
|
||||||
};
|
|
||||||
diskDataList.Add(diskReadKb);
|
|
||||||
var diskWriteKb = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = disk[3],
|
|
||||||
DataName = $"磁盘每秒写入数据量-{dev}" ,
|
|
||||||
DataType = $"DiskWriteKB-{dev}"
|
|
||||||
};
|
|
||||||
diskDataList.Add(diskWriteKb);
|
|
||||||
var diskAwait = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = disk[7],
|
|
||||||
DataName = $"磁盘平均等待时间-{dev}" ,
|
|
||||||
DataType = $"DiskAwait-{dev}"
|
|
||||||
};
|
|
||||||
diskDataList.Add(diskAwait);
|
|
||||||
var diskUtil = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = disk[8],
|
|
||||||
DataName = $"磁盘利用率-{dev}" ,
|
|
||||||
DataType = $"DiskUtil-{dev}"
|
|
||||||
};
|
|
||||||
diskDataList.Add(diskUtil);
|
|
||||||
|
|
||||||
diskDataList.ForEach(Action);
|
|
||||||
continue;
|
|
||||||
|
|
||||||
async void Action(ServerMonitoringData data)
|
|
||||||
{
|
|
||||||
await hubContext.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data);
|
|
||||||
await dataHelper.CheckData(server.Id, data.DataType ?? "", data.Data ?? "", data.DataName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
diskDataListAll.AddRange(diskDataList);
|
|
||||||
}
|
|
||||||
_count++;
|
|
||||||
if (_count <= 10) return;
|
|
||||||
_count = 0;
|
|
||||||
await dataHelper.SaveData(diskDataListAll);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
using LoongPanel_Asp.Models;
|
||||||
|
using LoongPanel_Asp.Servers;
|
||||||
|
using Quartz;
|
||||||
|
|
||||||
|
namespace LoongPanel_Asp.Jobs;
|
||||||
|
|
||||||
|
public abstract class IiJob(
|
||||||
|
DataService dataService,
|
||||||
|
SshService sshService)
|
||||||
|
: IJob
|
||||||
|
{
|
||||||
|
public async Task Execute(IJobExecutionContext context)
|
||||||
|
{
|
||||||
|
var dataMap = context.JobDetail.JobDataMap;
|
||||||
|
var serverList = (List<ServerModel>)dataMap["executor"];
|
||||||
|
var res = new List<ServerMonitoringData>();
|
||||||
|
foreach (var server in serverList)
|
||||||
|
{
|
||||||
|
var r = new List<ServerMonitoringData>();
|
||||||
|
await ExecuteInternal(context, server, sshService, r);
|
||||||
|
r.ForEach(x => x.ServerId = server.Id);
|
||||||
|
res.AddRange(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
await dataService.Save(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Task ExecuteInternal(IJobExecutionContext context, ServerModel server, SshService sshService,
|
||||||
|
List<ServerMonitoringData> res);
|
||||||
|
}
|
||||||
|
|
||||||
|
//DataService Submit
|
||||||
|
public class DataJob(DataService service) : IJob
|
||||||
|
{
|
||||||
|
public Task Execute(IJobExecutionContext context)
|
||||||
|
{
|
||||||
|
_ = service.Submit();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,99 +1,35 @@
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using LoongPanel_Asp.Helpers;
|
using LoongPanel_Asp.Helpers;
|
||||||
using LoongPanel_Asp.Hubs;
|
|
||||||
using LoongPanel_Asp.Models;
|
using LoongPanel_Asp.Models;
|
||||||
using LoongPanel_Asp.Servers;
|
using LoongPanel_Asp.Servers;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
|
||||||
namespace LoongPanel_Asp.Jobs;
|
namespace LoongPanel_Asp.Jobs;
|
||||||
|
|
||||||
public class MemoryTotalJob(
|
public class MemoryTotalJob(DataService dataService, SshService sshService) : IiJob(dataService, sshService)
|
||||||
IHubContext<SessionHub> hubContext,
|
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
ApplicationDbContext dbContext) : IJob
|
|
||||||
{
|
{
|
||||||
private static int _count;
|
protected override async Task ExecuteInternal(IJobExecutionContext context, ServerModel server,
|
||||||
|
SshService sshService, List<ServerMonitoringData> res)
|
||||||
public async Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
{
|
||||||
var dataMap = context.JobDetail.JobDataMap;
|
var output =
|
||||||
var dataHelper = new DataHelper(dbContext,hubContext);
|
await sshService.ExecuteCommandAsync(server.Id, "free -w | awk 'NR>1' | awk '{$1=\"\";print$0}' | xargs");
|
||||||
// 从JobDataMap中获取参数
|
var values = output.Split(" ", StringSplitOptions.RemoveEmptyEntries);
|
||||||
var serverList = (List<ServerModel>)dataMap["executor"];
|
var memoryTotal = values[0];
|
||||||
|
var memoryUsed = values[1];
|
||||||
|
var memoryFree = values[2];
|
||||||
|
var swapTotal = values[6];
|
||||||
|
var swapUsed = values[7];
|
||||||
|
DataHelper.AddMonitoringData(res, memoryUsed, "内存使用量", "MemoryUsed");
|
||||||
|
DataHelper.AddMonitoringData(res, memoryFree, "内存空闲量", "MemoryFree");
|
||||||
|
var memoryUsedPercent = Math.Round(double.Parse(memoryUsed) / double.Parse(memoryTotal) * 100, 2)
|
||||||
|
.ToString(CultureInfo.InvariantCulture);
|
||||||
|
DataHelper.AddMonitoringData(res, memoryUsedPercent, "内存总使用率", "MemoryTotalUsage");
|
||||||
|
|
||||||
var serverDataListAll = new List<ServerMonitoringData>();
|
if (double.Parse(swapTotal) > 0)
|
||||||
foreach (var server in serverList)
|
|
||||||
{
|
{
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
var swapUsedPercent = Math.Round(double.Parse(swapUsed) / double.Parse(swapTotal) * 100, 2)
|
||||||
var output = await sshClient?.ExecuteCommandAsync(server.Id, "free -w", "|", "awk 'NR>1'", "|",
|
.ToString(CultureInfo.InvariantCulture);
|
||||||
"awk '{$1=\"\";print $0}'", "|", "xargs")!;
|
DataHelper.AddMonitoringData(res, swapUsedPercent, "Swap总使用率", "SwapTotalUsage");
|
||||||
if (string.IsNullOrEmpty(output)) continue;
|
|
||||||
var serverDataList = new List<ServerMonitoringData>();
|
|
||||||
var dataList = output.Split(" ", StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
var memoryProperties = new List<Property>
|
|
||||||
{
|
|
||||||
new() { Name = "MemoryTotal", DisplayName = "内存总量", Order = 0 },
|
|
||||||
new() { Name = "MemoryUsed", DisplayName = "内存使用量", Order = 1 },
|
|
||||||
new() { Name = "MemoryFree", DisplayName = "内存空闲量", Order = 2 },
|
|
||||||
new() { Name = "MemoryCache", DisplayName = "内存缓存量", Order = 5 },
|
|
||||||
new() { Name = "SwapTotal", DisplayName = "Swap总量", Order = 7 },
|
|
||||||
new() { Name = "SwapUsed", DisplayName = "Swap使用量", Order = 8 },
|
|
||||||
new() { Name = "SwapFree", DisplayName = "Swap空闲量", Order = 9 }
|
|
||||||
};
|
|
||||||
memoryProperties.ForEach(data =>
|
|
||||||
{
|
|
||||||
var d = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = double.Parse(dataList[(int)data.Order!]).ToString(CultureInfo.CurrentCulture),
|
|
||||||
DataName = data.DisplayName,
|
|
||||||
DataType = data.Name
|
|
||||||
};
|
|
||||||
serverDataList.Add(d);
|
|
||||||
});
|
|
||||||
//计算内存使用率
|
|
||||||
var memoryUsed = double.Parse(dataList[1]);
|
|
||||||
var memoryTotal = double.Parse(dataList[0]);
|
|
||||||
if (memoryTotal <= 0) memoryTotal = 1;
|
|
||||||
var memoryUsedRate = memoryUsed / memoryTotal * 100;
|
|
||||||
|
|
||||||
var memoryData = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = memoryUsedRate.ToString(CultureInfo.InvariantCulture),
|
|
||||||
DataName = "内存总使用率",
|
|
||||||
DataType = "MemoryTotalUsage"
|
|
||||||
};
|
|
||||||
serverDataList.Add(memoryData);
|
|
||||||
//计算交换使用率
|
|
||||||
var swapUsed = double.Parse(dataList[8]);
|
|
||||||
var swapTotal = double.Parse(dataList[7]);
|
|
||||||
if (swapTotal <= 0) swapTotal = 1;
|
|
||||||
var swapUsedRate = swapUsed / swapTotal * 100;
|
|
||||||
var swapData = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = swapUsedRate.ToString(CultureInfo.InvariantCulture),
|
|
||||||
DataName = "Swap使用率",
|
|
||||||
DataType = "SwapTotalUsage"
|
|
||||||
};
|
|
||||||
serverDataList.Add(swapData);
|
|
||||||
|
|
||||||
serverDataList.ForEach(Action);
|
|
||||||
serverDataListAll.AddRange(serverDataList);
|
|
||||||
continue;
|
|
||||||
|
|
||||||
async void Action(ServerMonitoringData data)
|
|
||||||
{
|
|
||||||
await hubContext.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data);
|
|
||||||
await dataHelper.CheckData(server.Id, data.DataType ?? "", data.Data ?? "", data.DataName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_count++;
|
|
||||||
if (_count <= 10) return;
|
|
||||||
_count = 0;
|
|
||||||
await dataHelper.SaveData(serverDataListAll);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,84 +1,33 @@
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using LoongPanel_Asp.Helpers;
|
using LoongPanel_Asp.Helpers;
|
||||||
using LoongPanel_Asp.Hubs;
|
|
||||||
using LoongPanel_Asp.Models;
|
using LoongPanel_Asp.Models;
|
||||||
using LoongPanel_Asp.Servers;
|
using LoongPanel_Asp.Servers;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
|
||||||
namespace LoongPanel_Asp.Jobs;
|
namespace LoongPanel_Asp.Jobs;
|
||||||
|
|
||||||
public class NetworkTotalJob(
|
public class NetworkTotalJob(DataService dataService, SshService sshService) : IiJob(dataService, sshService)
|
||||||
IHubContext<SessionHub> hubContext,
|
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
ApplicationDbContext dbContext) : IJob
|
|
||||||
{
|
{
|
||||||
private static int _count;
|
protected override async Task ExecuteInternal(IJobExecutionContext context, ServerModel server,
|
||||||
|
SshService sshService, List<ServerMonitoringData> res)
|
||||||
public async Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
{
|
||||||
var dataMap = context.JobDetail.JobDataMap;
|
var output = await sshService.ExecuteCommandAsync(server.Id, "sar", "-n", "DEV", "3 1", "|", "grep", "Average:",
|
||||||
var dataHelper = new DataHelper(dbContext,hubContext);
|
"|", "awk 'NR>1'", "|", "awk '{$1=\"\";print $0}'");
|
||||||
var serverList = (List<ServerModel>)dataMap["executor"];
|
if (string.IsNullOrEmpty(output)) return;
|
||||||
var netWorkDataListAll = new List<ServerMonitoringData>();
|
var lines = output.Split("\n", StringSplitOptions.RemoveEmptyEntries);
|
||||||
foreach (var server in serverList)
|
var totalUsage = 0.0;
|
||||||
|
foreach (var line in lines)
|
||||||
{
|
{
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
if (string.IsNullOrEmpty(line)) continue;
|
||||||
var netWorkDataList = new List<ServerMonitoringData>();
|
var values = line.Split(" ", StringSplitOptions.RemoveEmptyEntries);
|
||||||
var output = await sshClient?.ExecuteCommandAsync(server.Id, "sar", "-n", "DEV", "3 1", "|", "grep",
|
var iFace = values[0];
|
||||||
"Average:", "|", "awk 'NR>1'", "|", "awk '{$1=\"\";print $0}'")!;
|
DataHelper.AddMonitoringData(res, values[1], $"每秒钟接收到的数据包数量-{iFace}", $"NetWorkReceive-{iFace}");
|
||||||
if (string.IsNullOrEmpty(output)) continue;
|
DataHelper.AddMonitoringData(res, values[2], $"每秒钟发送的数据包数量-{iFace}", $"NetWorkSend-{iFace}");
|
||||||
var lines = output.Split("\n");
|
DataHelper.AddMonitoringData(res, values[8], $"网络接口的使用率-{iFace}", $"NetWorkUsage-{iFace}");
|
||||||
var totalUsage = 0.0;
|
totalUsage += double.Parse(values[8]);
|
||||||
foreach (var line in lines)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(line)) continue;
|
|
||||||
var data = line.Split(" ", StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
var iFace = data[0];
|
|
||||||
var dataNum = data.Skip(1).Select(double.Parse).ToList();
|
|
||||||
var netWorkProperties = new List<Property>
|
|
||||||
{
|
|
||||||
new() { Name = "ReceivedPacketsPerSecond", DisplayName = "每秒钟接收到的数据包数量", Order = 0 },
|
|
||||||
new() { Name = "TransmittedPacketsPerSecond", DisplayName = "每秒钟发送的数据包数量", Order = 1 },
|
|
||||||
new() { Name = "InterfaceUtilizationPercentage", DisplayName = "网络接口的使用率", Order = 7 }
|
|
||||||
};
|
|
||||||
netWorkProperties.ForEach(property =>
|
|
||||||
{
|
|
||||||
var d = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = dataNum[(int)property.Order!].ToString(CultureInfo.InvariantCulture),
|
|
||||||
DataName = $"{property.DisplayName}-{iFace}",
|
|
||||||
DataType = $"{property.Name}-{iFace}"
|
|
||||||
};
|
|
||||||
netWorkDataList.Add(d);
|
|
||||||
});
|
|
||||||
totalUsage += dataNum[7];
|
|
||||||
}
|
|
||||||
|
|
||||||
var d = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = totalUsage.ToString(CultureInfo.InvariantCulture),
|
|
||||||
DataName = "网络接口总体使用率",
|
|
||||||
DataType = "InterfaceTotalUtilizationPercentage"
|
|
||||||
};
|
|
||||||
netWorkDataList.Add(d);
|
|
||||||
netWorkDataListAll.AddRange(netWorkDataList);
|
|
||||||
|
|
||||||
netWorkDataList.ForEach( Action);
|
|
||||||
continue;
|
|
||||||
|
|
||||||
async void Action(ServerMonitoringData data)
|
|
||||||
{
|
|
||||||
await hubContext.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data);
|
|
||||||
await dataHelper.CheckData(server.Id, data.DataType ?? "", data.Data ?? "", data.DataName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_count++;
|
DataHelper.AddMonitoringData(res, totalUsage.ToString(CultureInfo.InvariantCulture), "网络接口的使用率-总",
|
||||||
if (_count <= 10) return;
|
"NetWorkUsage");
|
||||||
_count = 0;
|
|
||||||
await dataHelper.SaveData(netWorkDataListAll);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,106 +1,34 @@
|
||||||
using LoongPanel_Asp.Helpers;
|
using LoongPanel_Asp.Helpers;
|
||||||
using LoongPanel_Asp.Hubs;
|
|
||||||
using LoongPanel_Asp.Models;
|
using LoongPanel_Asp.Models;
|
||||||
using LoongPanel_Asp.Servers;
|
using LoongPanel_Asp.Servers;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
|
||||||
namespace LoongPanel_Asp.Jobs;
|
namespace LoongPanel_Asp.Jobs;
|
||||||
|
|
||||||
public class ProcessTotalJob(
|
public class ProcessTotalJob(DataService dataService, SshService sshService) : IiJob(dataService, sshService)
|
||||||
IHubContext<SessionHub> hubContext,
|
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
ApplicationDbContext dbContext) : IJob
|
|
||||||
{
|
{
|
||||||
private static int _count;
|
protected override async Task ExecuteInternal(IJobExecutionContext context, ServerModel server,
|
||||||
|
SshService sshService, List<ServerMonitoringData> res)
|
||||||
public async Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
{
|
||||||
var dataMap = context.JobDetail.JobDataMap;
|
var output = await sshService.ExecuteCommandAsync(server.Id, "ps", "-e", "|", "wc", "-l");
|
||||||
var dataHelper = new DataHelper(dbContext,hubContext);
|
if (string.IsNullOrEmpty(output)) return;
|
||||||
var serverList = (List<ServerModel>)dataMap["executor"];
|
var value = int.Parse(output).ToString();
|
||||||
var processDataListAll = new List<ServerMonitoringData>();
|
DataHelper.AddMonitoringData(res, value, "进程总数", "ProcessTotalCount");
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
output = await sshService.ExecuteCommandAsync(server.Id, "ps", "-eLf", "|", "wc", "-l");
|
||||||
foreach (var server in serverList)
|
if (string.IsNullOrEmpty(output)) return;
|
||||||
{
|
value = int.Parse(output).ToString();
|
||||||
var output = await sshClient?.ExecuteCommandAsync(server.Id, "ps", "-e", "|", "wc", "-l")!;
|
DataHelper.AddMonitoringData(res, value, "线程总数", "ThreadTotalCount");
|
||||||
if (string.IsNullOrEmpty(output)) continue;
|
|
||||||
var processDataList = new List<ServerMonitoringData>();
|
|
||||||
var count = int.Parse(output);
|
|
||||||
var processTotalCount = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = count.ToString(),
|
|
||||||
DataName = "进程总数",
|
|
||||||
DataType = "ProcessTotalCount"
|
|
||||||
};
|
|
||||||
processDataList.Add(processTotalCount);
|
|
||||||
output = await sshClient?.ExecuteCommandAsync(server.Id, "ps", "-eLf", "|", "wc", "-l")!;
|
|
||||||
if (string.IsNullOrEmpty(output)) continue;
|
|
||||||
count = int.Parse(output);
|
|
||||||
var threadsTotalCount = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = count.ToString(),
|
|
||||||
DataName = "线程总数",
|
|
||||||
DataType = "ThreadsTotalCount"
|
|
||||||
};
|
|
||||||
processDataList.Add(threadsTotalCount);
|
|
||||||
|
|
||||||
processDataList.ForEach(Action);
|
|
||||||
processDataListAll.AddRange(processDataList);
|
|
||||||
continue;
|
|
||||||
|
|
||||||
async void Action(ServerMonitoringData data)
|
|
||||||
{
|
|
||||||
await hubContext.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data);
|
|
||||||
await dataHelper.CheckData(server.Id, data.DataType ?? "", data.Data ?? "", data.DataName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_count++;
|
|
||||||
if (_count <= 10) return;
|
|
||||||
_count = 0;
|
|
||||||
await dataHelper.SaveData(processDataListAll);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PhrasePatternJob(
|
public class PhrasePatternJob(DataService dataService, SshService sshService) : IiJob(dataService, sshService)
|
||||||
IHubContext<SessionHub> hubContext,
|
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
ApplicationDbContext dbContext) : IJob
|
|
||||||
{
|
{
|
||||||
private static int _count;
|
protected override async Task ExecuteInternal(IJobExecutionContext context, ServerModel server,
|
||||||
|
SshService sshService, List<ServerMonitoringData> res)
|
||||||
public async Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
{
|
||||||
var dataMap = context.JobDetail.JobDataMap;
|
var output = await sshService.ExecuteCommandAsync(server.Id, "lsof", "|", "wc", "-l");
|
||||||
var dataHelper = new DataHelper(dbContext,hubContext);
|
if (string.IsNullOrEmpty(output)) return;
|
||||||
var serverList = (List<ServerModel>)dataMap["executor"];
|
var value = int.Parse(output).ToString();
|
||||||
var processDataListAll = new List<ServerMonitoringData>();
|
DataHelper.AddMonitoringData(res, value, "句柄", "PhrasePatternCount");
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
|
||||||
foreach (var server in serverList)
|
|
||||||
{
|
|
||||||
var output = await sshClient?.ExecuteCommandAsync(server.Id, "lsof", "|", "wc", "-l")!;
|
|
||||||
if (string.IsNullOrEmpty(output)) continue;
|
|
||||||
var count = int.Parse(output);
|
|
||||||
var phrasePatternCount = new ServerMonitoringData
|
|
||||||
{
|
|
||||||
ServerId = server.Id,
|
|
||||||
Data = count.ToString(),
|
|
||||||
DataName = "句柄",
|
|
||||||
DataType = "PhrasePatternCount"
|
|
||||||
};
|
|
||||||
processDataListAll.Add(phrasePatternCount);
|
|
||||||
await hubContext.Clients.All.SendAsync("ReceiveData", server.Id, phrasePatternCount.DataType,
|
|
||||||
phrasePatternCount.Data);
|
|
||||||
await dataHelper.CheckData(server.Id, phrasePatternCount.DataType ?? "", phrasePatternCount.Data ?? "",
|
|
||||||
"句柄数");
|
|
||||||
}
|
|
||||||
|
|
||||||
_count++;
|
|
||||||
if (_count <= 10) return;
|
|
||||||
_count = 0;
|
|
||||||
await dataHelper.SaveData(processDataListAll);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
namespace LoongPanel_Asp.Jobs;
|
||||||
|
|
||||||
|
// public class SystemTimeIntervalLoadJob(DataService dataService, SshService sshService) : IiJob(dataService, sshService)
|
||||||
|
// {
|
||||||
|
// protected override async Task<List<ServerMonitoringData>?> ExecuteInternal(IJobExecutionContext context, ServerModel server, SshService sshService)
|
||||||
|
// {
|
||||||
|
// var output = await sshService?.ExecuteCommandAsync(server.Id, "sar", "-u", "3 1", "|", "grep", "Average")!;
|
||||||
|
// if (string.IsNullOrEmpty(output)) return null;
|
||||||
|
// var totalUsage = new ServerMonitoringData
|
||||||
|
// {
|
||||||
|
// ServerId = server.Id,
|
||||||
|
// Data = "20",
|
||||||
|
// DataName = "CPU总使用率",
|
||||||
|
// DataType = "CpuTotalUsage"
|
||||||
|
// };
|
||||||
|
// return new List<ServerMonitoringData> { totalUsage };
|
||||||
|
// }
|
||||||
|
// }
|
|
@ -1,76 +1,27 @@
|
||||||
using LoongPanel_Asp.Helpers;
|
using LoongPanel_Asp.Helpers;
|
||||||
using LoongPanel_Asp.Hubs;
|
|
||||||
using LoongPanel_Asp.Models;
|
using LoongPanel_Asp.Models;
|
||||||
using LoongPanel_Asp.Servers;
|
using LoongPanel_Asp.Servers;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
|
||||||
namespace LoongPanel_Asp.Jobs;
|
namespace LoongPanel_Asp.Jobs;
|
||||||
|
|
||||||
public class UserTotalJob(IHubContext<SessionHub> hubContext,
|
public class UserTotalJob(DataService dataService, SshService sshService) : IiJob(dataService, sshService)
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
ApplicationDbContext dbContext): IJob
|
|
||||||
{
|
{
|
||||||
private static int _count;
|
protected override async Task ExecuteInternal(IJobExecutionContext context, ServerModel server,
|
||||||
public async Task Execute(IJobExecutionContext context)
|
SshService sshService, List<ServerMonitoringData> res)
|
||||||
{
|
{
|
||||||
// 执行用户统计任务
|
var output = await sshService.ExecuteCommandAsync(server.Id,
|
||||||
var dataMap = context.JobDetail.JobDataMap;
|
"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");
|
||||||
var dataHelper = new DataHelper(dbContext,hubContext);
|
if (string.IsNullOrEmpty(output)) return;
|
||||||
var serverList = (List<ServerModel>)dataMap["executor"];
|
var users = output.Split("\n", StringSplitOptions.RemoveEmptyEntries);
|
||||||
var userDataListAll = new List<ServerMonitoringData>();
|
|
||||||
var sshClient = serviceProvider.GetService<SshService>();
|
foreach (var user in users)
|
||||||
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")!;
|
var values = user.Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList();
|
||||||
if (string.IsNullOrEmpty(output)) continue;
|
var name = values[0];
|
||||||
var lines = output.Split("\n",StringSplitOptions.RemoveEmptyEntries);
|
DataHelper.AddMonitoringData(res, values[1], $"CPU使用率-{name}", $"CpuUsage-{name}");
|
||||||
|
DataHelper.AddMonitoringData(res, values[2], $"内存使用率-{name}", $"MemUsage-{name}");
|
||||||
foreach (var line in lines)
|
DataHelper.AddMonitoringData(res, values[3], $"用户进程数-{name}", $"UserProcessCount-{name}");
|
||||||
{
|
|
||||||
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(Action);
|
|
||||||
_count++;
|
|
||||||
if (_count <= 10) return;
|
|
||||||
_count = 0;
|
|
||||||
await dataHelper.SaveData(userDataListAll);
|
|
||||||
return;
|
|
||||||
|
|
||||||
async void Action(ServerMonitoringData data)
|
|
||||||
{
|
|
||||||
await hubContext.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data);
|
|
||||||
await dataHelper.CheckData(data.ServerId, data.DataType ?? "", data.Data ?? "", data.DataName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,7 +18,8 @@
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6"/>
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6"/>
|
||||||
<PackageReference Include="MimeKit" Version="4.6.0"/>
|
<PackageReference Include="MimeKit" Version="4.6.0"/>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||||
|
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.11" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4"/>
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4"/>
|
||||||
<PackageReference Include="Quartz" Version="3.9.0"/>
|
<PackageReference Include="Quartz" Version="3.9.0"/>
|
||||||
<PackageReference Include="Quartz.AspNetCore" Version="3.9.0"/>
|
<PackageReference Include="Quartz.AspNetCore" Version="3.9.0"/>
|
||||||
|
@ -26,7 +27,7 @@
|
||||||
<PackageReference Include="SSH.NET" Version="2024.0.0"/>
|
<PackageReference Include="SSH.NET" Version="2024.0.0"/>
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.0"/>
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.0"/>
|
||||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
<PackageReference Include="System.Linq.Async" Version="6.0.1"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -35,23 +36,20 @@
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Configs\servers.ini">
|
<None Update="Configs\servers.ini">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="markdowns\templates\巡检模板1.md">
|
<None Update="markdowns\templates\巡检模板1.md">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
|
||||||
<None Update="app.db">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Configs\Alerts\" />
|
<Folder Include="Configs\Alerts\"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Update="wwwroot\index">
|
<Content Update="wwwroot\index">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -16,13 +16,13 @@ public class ApiPermissionMiddleware(
|
||||||
// 获取配置中定义的公开API列表
|
// 获取配置中定义的公开API列表
|
||||||
var publicApis = configuration["PublicApi"]?.Split(";", StringSplitOptions.RemoveEmptyEntries) ??
|
var publicApis = configuration["PublicApi"]?.Split(";", StringSplitOptions.RemoveEmptyEntries) ??
|
||||||
[];
|
[];
|
||||||
|
|
||||||
if (publicApis.Any(api => context.Request.Path.Value == api))
|
if (publicApis.Any(api => context.Request.Path.Value == api))
|
||||||
{
|
{
|
||||||
await next(context);
|
await next(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.Request.Path.Value!.StartsWith("/public"))
|
if (context.Request.Path.Value!.StartsWith("/public"))
|
||||||
{
|
{
|
||||||
await next(context);
|
await next(context);
|
||||||
|
@ -84,14 +84,10 @@ public class ApiPermissionMiddleware(
|
||||||
// 判断请求是否拥有权限
|
// 判断请求是否拥有权限
|
||||||
if (!apiPermissions.Any(x =>
|
if (!apiPermissions.Any(x =>
|
||||||
context.Request.Path.Value != null && context.Request.Path.Value.StartsWith(x)))
|
context.Request.Path.Value != null && context.Request.Path.Value.StartsWith(x)))
|
||||||
{
|
|
||||||
await ForbiddenResponse(context);
|
await ForbiddenResponse(context);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
// 请求拥有权限,调用下一个中间件
|
// 请求拥有权限,调用下一个中间件
|
||||||
await next(context);
|
await next(context);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,6 +18,7 @@ public class PermissionMiddleware(
|
||||||
await next(context);
|
await next(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine(context.Request.Path.Value!);
|
Console.WriteLine(context.Request.Path.Value!);
|
||||||
//如果访问 /public/*
|
//如果访问 /public/*
|
||||||
if (context.Request.Path.Value!.StartsWith("/public"))
|
if (context.Request.Path.Value!.StartsWith("/public"))
|
||||||
|
@ -25,7 +26,7 @@ public class PermissionMiddleware(
|
||||||
await next(context);
|
await next(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取请求头中的Authorization信息
|
// 获取请求头中的Authorization信息
|
||||||
var authorizationHeader = context.Request.Headers["Authorization"];
|
var authorizationHeader = context.Request.Headers["Authorization"];
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ public class EmailModel
|
||||||
public required string Email { get; set; }
|
public required string Email { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class RegisterModel : EmailModel
|
public class RegisterModel : EmailModel
|
||||||
{
|
{
|
||||||
public required string UserName { get; set; }
|
public required string UserName { get; set; }
|
||||||
|
|
|
@ -7,10 +7,9 @@ public class Property
|
||||||
public int? Order { get; set; }
|
public int? Order { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class ServerModel
|
public class ServerModel
|
||||||
{
|
{
|
||||||
public required string Id { get;init; }
|
public required string Id { get; init; }
|
||||||
public required string Address { get; set; }
|
public required string Address { get; set; }
|
||||||
public required int Port { get; set; }
|
public required int Port { get; set; }
|
||||||
public required string ServerName { get; set; }
|
public required string ServerName { get; set; }
|
||||||
|
@ -19,11 +18,10 @@ public class ServerModel
|
||||||
public required bool Http { get; set; }
|
public required bool Http { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class EmailSettings
|
public class EmailSettings
|
||||||
{
|
{
|
||||||
public required string Host {get;init;}
|
public required string Host { get; init; }
|
||||||
public required string Port {get;init;}
|
public required string Port { get; init; }
|
||||||
public required string Username {get;init;}
|
public required string Username { get; init; }
|
||||||
public required string Password {get;init;}
|
public required string Password { get; init; }
|
||||||
}
|
}
|
|
@ -3,11 +3,10 @@ using LoongPanel_Asp;
|
||||||
using LoongPanel_Asp.Helpers;
|
using LoongPanel_Asp.Helpers;
|
||||||
using LoongPanel_Asp.Hubs;
|
using LoongPanel_Asp.Hubs;
|
||||||
using LoongPanel_Asp.Middlewares;
|
using LoongPanel_Asp.Middlewares;
|
||||||
using LoongPanel_Asp.Models;
|
|
||||||
using LoongPanel_Asp.Servers;
|
using LoongPanel_Asp.Servers;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NLog.Web;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
using Quartz.AspNetCore;
|
using Quartz.AspNetCore;
|
||||||
|
|
||||||
|
@ -77,7 +76,8 @@ builder.Services.AddCors(options =>
|
||||||
{
|
{
|
||||||
//允许全部
|
//允许全部
|
||||||
policy.WithOrigins("http://localhost:3001", "http://192.168.1.22:3001", "http://192.168.1.22:3001",
|
policy.WithOrigins("http://localhost:3001", "http://192.168.1.22:3001", "http://192.168.1.22:3001",
|
||||||
"https://192.168.0.22:3000","https://loongpanel.xn--7p0a.site").AllowAnyHeader().AllowAnyMethod().AllowCredentials();
|
"https://192.168.0.22:3000", "https://loongpanel.xn--7p0a.site").AllowAnyHeader().AllowAnyMethod()
|
||||||
|
.AllowCredentials();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -104,12 +104,12 @@ builder.Services.AddQuartzServer(options =>
|
||||||
|
|
||||||
builder.Services.AddScoped<SshService>();
|
builder.Services.AddScoped<SshService>();
|
||||||
builder.Services.AddSingleton<SshStreamService>();
|
builder.Services.AddSingleton<SshStreamService>();
|
||||||
|
builder.Services.AddSingleton<DataService>();
|
||||||
|
|
||||||
builder.Services.AddHostedService<Init>();
|
builder.Services.AddHostedService<Init>();
|
||||||
|
|
||||||
|
builder.Logging.ClearProviders();
|
||||||
|
builder.Host.UseNLog();
|
||||||
|
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
using System.Globalization;
|
||||||
|
using LoongPanel_Asp.Hubs;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
|
namespace LoongPanel_Asp.Servers;
|
||||||
|
|
||||||
|
public class DataService(IServiceProvider serviceProvider, IHubContext<SessionHub> context,ILogger<DataService> logger) : IDisposable
|
||||||
|
{
|
||||||
|
//创建一个存储
|
||||||
|
private static readonly Dictionary<string, ServerMonitoringData> ServerMonitoringData = new();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Submit().Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Save(ServerMonitoringData data)
|
||||||
|
{
|
||||||
|
data.Time = DateTime.UtcNow;
|
||||||
|
//取两位小数
|
||||||
|
data.Data = Math.Round(double.Parse(data.Data!), 2).ToString(CultureInfo.InvariantCulture);
|
||||||
|
ServerMonitoringData[$"{data.DataType}-{data.ServerId}"] = data;
|
||||||
|
logger.LogInformation($"发送数据{data.DataType}:{data.Data}成功");
|
||||||
|
await context.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Save(List<ServerMonitoringData> datas)
|
||||||
|
{
|
||||||
|
datas.ForEach(x =>
|
||||||
|
{
|
||||||
|
x.Time = DateTime.UtcNow;
|
||||||
|
x.Data = Math.Round(double.Parse(x.Data!), 2).ToString(CultureInfo.InvariantCulture);
|
||||||
|
});
|
||||||
|
foreach (var data in datas)
|
||||||
|
{
|
||||||
|
ServerMonitoringData[$"{data.DataType}-{data.ServerId}"] = data;
|
||||||
|
logger.LogInformation($"发送数据{data.DataType}:{data.Data}成功");
|
||||||
|
await context.Clients.All.SendAsync("ReceiveData", data.ServerId, data.DataType, data.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//提交
|
||||||
|
public async Task Submit()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var scope = serviceProvider.CreateScope())
|
||||||
|
{
|
||||||
|
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
var dataDb = dbContext.ServerMonitoringData;
|
||||||
|
await dataDb.AddRangeAsync(ServerMonitoringData.Values);
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
logger.LogInformation($"监控数据已保存,共保存 {ServerMonitoringData.Count} 条记录。");
|
||||||
|
Console.WriteLine($"监控数据已保存,共保存 {ServerMonitoringData.Count} 条记录。");
|
||||||
|
}
|
||||||
|
ServerMonitoringData.Clear();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// 记录异常信息
|
||||||
|
logger.LogError(ex, "保存监控数据时发生错误。");
|
||||||
|
// 根据需要重新抛出异常或进行其他错误处理
|
||||||
|
Console.WriteLine(ex.Message);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CheckData()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,54 +1,61 @@
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Net;
|
|
||||||
using LoongPanel_Asp.Models;
|
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
||||||
|
|
||||||
namespace LoongPanel_Asp.Servers;
|
namespace LoongPanel_Asp.Servers;
|
||||||
|
|
||||||
public class EmailService:IDisposable
|
public class EmailService : IDisposable
|
||||||
{
|
{
|
||||||
|
|
||||||
private readonly SmtpClient _smtpClient;
|
|
||||||
private readonly IConfigurationSection _emailSettings;
|
private readonly IConfigurationSection _emailSettings;
|
||||||
|
|
||||||
|
private readonly SmtpClient _smtpClient;
|
||||||
|
|
||||||
//创建一个验证码缓存字典
|
//创建一个验证码缓存字典
|
||||||
private readonly ConcurrentDictionary<string, string> _verifyCodeCache = new();
|
private readonly ConcurrentDictionary<string, string> _verifyCodeCache = new();
|
||||||
|
|
||||||
public EmailService(IConfigurationSection emailSettings)
|
public EmailService(IConfigurationSection emailSettings)
|
||||||
{
|
{
|
||||||
_emailSettings = emailSettings;
|
_emailSettings = emailSettings;
|
||||||
// 初始化SmtpClient
|
// 初始化SmtpClient
|
||||||
_smtpClient = new SmtpClient();
|
_smtpClient = new SmtpClient();
|
||||||
_smtpClient.Connect(_emailSettings["Host"], int.Parse(_emailSettings["Port"] ?? throw new InvalidOperationException()), true);
|
_smtpClient.Connect(_emailSettings["Host"],
|
||||||
|
int.Parse(_emailSettings["Port"] ?? throw new InvalidOperationException()), true);
|
||||||
_smtpClient.Authenticate(_emailSettings["Username"], _emailSettings["Password"]);
|
_smtpClient.Authenticate(_emailSettings["Username"], _emailSettings["Password"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_smtpClient.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//发送邮件
|
//发送邮件
|
||||||
public async Task SendEmailAsync(string toAddress,string toName, string subject, string body)
|
public async Task SendEmailAsync(string toAddress, string toName, string subject, string body)
|
||||||
{
|
{
|
||||||
using var mailMessage = new MimeMessage();
|
using var mailMessage = new MimeMessage();
|
||||||
mailMessage.From.Add(new MailboxAddress("龙盾云御",_emailSettings["Username"]));
|
mailMessage.From.Add(new MailboxAddress("龙盾云御", _emailSettings["Username"]));
|
||||||
mailMessage.To.Add(new MailboxAddress(toName, toAddress));
|
mailMessage.To.Add(new MailboxAddress(toName, toAddress));
|
||||||
mailMessage.Subject = subject;
|
mailMessage.Subject = subject;
|
||||||
var bodyBuilder = new BodyBuilder
|
var bodyBuilder = new BodyBuilder
|
||||||
{
|
{
|
||||||
HtmlBody = body,
|
HtmlBody = body
|
||||||
};
|
};
|
||||||
mailMessage.Body = bodyBuilder.ToMessageBody();
|
mailMessage.Body = bodyBuilder.ToMessageBody();
|
||||||
await _smtpClient.SendAsync(mailMessage);
|
await _smtpClient.SendAsync(mailMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//发送邮箱验证码
|
//发送邮箱验证码
|
||||||
public async Task SendEmailVerifyCodeAsync( string userId,string toAddress, string toName)
|
public async Task SendEmailVerifyCodeAsync(string userId, string toAddress, string toName)
|
||||||
{
|
{
|
||||||
const string subject = "龙盾云御邮箱验证码";
|
const string subject = "龙盾云御邮箱验证码";
|
||||||
var code=GenerateVerifyCode();
|
var code = GenerateVerifyCode();
|
||||||
var body = $"<h1>您的验证码是:{code}</h1>";
|
var body = $"<h1>您的验证码是:{code}</h1>";
|
||||||
await SendEmailAsync(toAddress, toName, subject, body);
|
await SendEmailAsync(toAddress, toName, subject, body);
|
||||||
//存入缓存
|
//存入缓存
|
||||||
_verifyCodeCache.AddOrUpdate(userId, code, (key, oldValue) => code);
|
_verifyCodeCache.AddOrUpdate(userId, code, (key, oldValue) => code);
|
||||||
}
|
}
|
||||||
|
|
||||||
//验证邮箱验证码
|
//验证邮箱验证码
|
||||||
public bool VerifyEmailVerifyCode(string userId, string code)
|
public bool VerifyEmailVerifyCode(string userId, string code)
|
||||||
{
|
{
|
||||||
|
@ -57,8 +64,8 @@ public class EmailService:IDisposable
|
||||||
_verifyCodeCache.TryRemove(userId, out _);
|
_verifyCodeCache.TryRemove(userId, out _);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//生成6位随机验证码
|
//生成6位随机验证码
|
||||||
private static string GenerateVerifyCode()
|
private static string GenerateVerifyCode()
|
||||||
{
|
{
|
||||||
|
@ -66,9 +73,4 @@ public class EmailService:IDisposable
|
||||||
var verifyCode = random.Next(100000, 999999).ToString();
|
var verifyCode = random.Next(100000, 999999).ToString();
|
||||||
return verifyCode;
|
return verifyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
using LoongPanel_Asp.Helpers;
|
using LoongPanel_Asp.Helpers;
|
||||||
using LoongPanel_Asp.Models;
|
|
||||||
using Renci.SshNet;
|
using Renci.SshNet;
|
||||||
using ConnectionInfo = Renci.SshNet.ConnectionInfo;
|
using ConnectionInfo = Renci.SshNet.ConnectionInfo;
|
||||||
|
|
||||||
|
@ -10,6 +9,8 @@ public class SshService : IDisposable
|
||||||
private readonly CancellationTokenSource _cts = new();
|
private readonly CancellationTokenSource _cts = new();
|
||||||
private readonly ApplicationDbContext _db;
|
private readonly ApplicationDbContext _db;
|
||||||
private readonly ILogger<SshService> _logger;
|
private readonly ILogger<SshService> _logger;
|
||||||
|
//注册nlog
|
||||||
|
|
||||||
private readonly Dictionary<string, ConnectionInfo> _serverConnectionInfos = new();
|
private readonly Dictionary<string, ConnectionInfo> _serverConnectionInfos = new();
|
||||||
private readonly Dictionary<string, SshClient> _serverSshClients = new();
|
private readonly Dictionary<string, SshClient> _serverSshClients = new();
|
||||||
|
|
||||||
|
@ -30,12 +31,13 @@ public class SshService : IDisposable
|
||||||
private void LoadServerConfigurations()
|
private void LoadServerConfigurations()
|
||||||
{
|
{
|
||||||
// 从数据库中加载服务器配置信息
|
// 从数据库中加载服务器配置信息
|
||||||
var serverConfigurations=JobConfigHelper.GetServers();
|
var serverConfigurations = JobConfigHelper.GetServers();
|
||||||
|
|
||||||
foreach (var serverConfiguration in serverConfigurations)
|
foreach (var serverConfiguration in serverConfigurations)
|
||||||
{
|
{
|
||||||
var connectionInfo = new ConnectionInfo(serverConfiguration.Address, serverConfiguration.Port,
|
var connectionInfo = new ConnectionInfo(serverConfiguration.Address, serverConfiguration.Port,
|
||||||
serverConfiguration.Username, new PasswordAuthenticationMethod(serverConfiguration.Username, serverConfiguration.Password));
|
serverConfiguration.Username,
|
||||||
|
new PasswordAuthenticationMethod(serverConfiguration.Username, serverConfiguration.Password));
|
||||||
_serverConnectionInfos[serverConfiguration.Id] = connectionInfo;
|
_serverConnectionInfos[serverConfiguration.Id] = connectionInfo;
|
||||||
var sshClient = new SshClient(connectionInfo);
|
var sshClient = new SshClient(connectionInfo);
|
||||||
// 设置超时时间
|
// 设置超时时间
|
||||||
|
@ -44,30 +46,39 @@ public class SshService : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> ExecuteCommandAsync(string serverId, string command, params string[] arguments)
|
public async Task<string?> ExecuteCommandAsync(string serverId, string command, params string[] arguments)
|
||||||
{
|
{
|
||||||
var sshClient = _serverSshClients[serverId];
|
if (!_serverSshClients.TryGetValue(serverId, out var sshClient))
|
||||||
if (sshClient == null) throw new InvalidOperationException($"SSH client for server ID '{serverId}' not found.");
|
{
|
||||||
|
var errorMessage = $"服务器ID '{serverId}' 的SSH客户端未找到。";
|
||||||
|
_logger.LogError(errorMessage);
|
||||||
|
throw new InvalidOperationException(errorMessage);
|
||||||
|
}
|
||||||
var output = "";
|
var output = "";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 确保在执行命令前连接到服务器
|
// 确保在执行命令前连接到服务器
|
||||||
if (!sshClient.IsConnected) await sshClient.ConnectAsync(_cts.Token);
|
if (!sshClient.IsConnected) await sshClient.ConnectAsync(_cts.Token);
|
||||||
var commandString = string.Join(" ", "LANG=C", command, string.Join(" ", arguments));
|
var commandString = string.Join(" ", "LANG=C", command, string.Join(" ", arguments));
|
||||||
Console.WriteLine(commandString);
|
_logger.LogInformation($"服务器ID:{serverId},正在执行命令:{commandString}");
|
||||||
using var commandResult = sshClient.RunCommand(commandString);
|
using var commandResult = sshClient.RunCommand(commandString);
|
||||||
output = commandResult.Result;
|
output = commandResult.Result;
|
||||||
if (commandResult.ExitStatus != 0) output = commandResult.Error;
|
if (commandResult.ExitStatus != 0)
|
||||||
|
{
|
||||||
|
_logger.LogError($"命令 {commandString} 执行失败,退出状态码 {commandResult.ExitStatus}:\n {output}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
_logger.LogInformation($"命令 {commandString} 执行成功:{output}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine(ex);
|
_logger.LogError(ex, "执行命令时发生错误。");
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<string> ExecuteCommandAsync(bool langC, string serverId, string command,
|
public async Task<string> ExecuteCommandAsync(bool langC, string serverId, string command,
|
||||||
params string[] arguments)
|
params string[] arguments)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,95 +1,25 @@
|
||||||
using Renci.SshNet;
|
using System.Collections.Concurrent;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.IO;
|
|
||||||
using LoongPanel_Asp.Helpers;
|
using LoongPanel_Asp.Helpers;
|
||||||
using LoongPanel_Asp.Hubs;
|
using LoongPanel_Asp.Hubs;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Renci.SshNet;
|
||||||
using ConnectionInfo = Renci.SshNet.ConnectionInfo;
|
using ConnectionInfo = Renci.SshNet.ConnectionInfo;
|
||||||
|
|
||||||
namespace LoongPanel_Asp.Servers;
|
namespace LoongPanel_Asp.Servers;
|
||||||
|
|
||||||
public class SshStreamService : IDisposable
|
public class SshStreamService : IDisposable
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<string, (SshClient Client, ShellStream Stream)> _sshStreams = new();
|
|
||||||
private readonly IHubContext<TerminalHub> _hubContext;
|
private readonly IHubContext<TerminalHub> _hubContext;
|
||||||
private CancellationTokenSource _cancellationTokenSource;
|
private readonly ConcurrentDictionary<string, (SshClient Client, ShellStream Stream)> _sshStreams = new();
|
||||||
private Task _readingTask;
|
private readonly CancellationTokenSource _cancellationTokenSource;
|
||||||
|
private readonly Task _readingTask;
|
||||||
|
|
||||||
public SshStreamService(IHubContext<TerminalHub> hubContext)
|
public SshStreamService(IHubContext<TerminalHub> hubContext)
|
||||||
{
|
{
|
||||||
_hubContext = hubContext;
|
_hubContext = hubContext;
|
||||||
_cancellationTokenSource = new CancellationTokenSource();
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
_readingTask = Task.Run(() => StartReadingAsync(_cancellationTokenSource.Token));
|
_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 ReSize(string userId,int col, int row)
|
|
||||||
{
|
|
||||||
//从sshStreams中获取对象
|
|
||||||
if (_sshStreams.TryGetValue(userId, out var tuple))
|
|
||||||
{
|
|
||||||
var (ssh, stream) = tuple;
|
|
||||||
if (ssh.IsConnected)
|
|
||||||
{
|
|
||||||
var ansiResizeCommand = $"stty cols {col}; stty rows {row}";
|
|
||||||
stream.WriteLine(ansiResizeCommand);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("SSH client is not connected or shell stream is not available.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("SSH connection not found for the given user ID.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
@ -111,9 +41,80 @@ public class SshStreamService : IDisposable
|
||||||
sshClient.Disconnect();
|
sshClient.Disconnect();
|
||||||
sshClient.Dispose();
|
sshClient.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_sshStreams.Clear();
|
_sshStreams.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 ReSize(string userId, int col, int row)
|
||||||
|
{
|
||||||
|
//从sshStreams中获取对象
|
||||||
|
if (_sshStreams.TryGetValue(userId, out var tuple))
|
||||||
|
{
|
||||||
|
var (ssh, stream) = tuple;
|
||||||
|
if (ssh.IsConnected)
|
||||||
|
{
|
||||||
|
var ansiResizeCommand = $"stty cols {col}; stty rows {row}";
|
||||||
|
stream.WriteLine(ansiResizeCommand);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("SSH client is not connected or shell stream is not available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("SSH connection not found for the given user ID.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task StartReadingAsync(CancellationToken cancellationToken)
|
private async Task StartReadingAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
| Öµ°à¼Ç¼ | col | col | col | col | col |
|
| ֵ<EFBFBD><EFBFBD><EFBFBD>¼ | col | col | col | col | col |
|
||||||
| -------- | ------- | ------- | ------- | ------- | ------- |
|
|---------|---------|---------|---------|---------|---------|
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
||||||
| content | content | content | content | content | content |
|
| content | content | content | content | content | content |
|
|
@ -8,21 +8,21 @@
|
||||||
|
|
||||||
## 第二部分:巡检项目
|
## 第二部分:巡检项目
|
||||||
|
|
||||||
| 序号 | 巡检项目名称 | 巡检要点 | 巡检内容 |
|
| 序号 | 巡检项目名称 | 巡检要点 | 巡检内容 |
|
||||||
| ---- | ------------ | -------------------------------------------------- | -------------------------------------------------------------- |
|
|-----|--------|---------------------------|---------------------------------|
|
||||||
| 1 | 设备运行状况 | 检查设备运行是否平稳,有无异常振动或噪音。 | 详细记录设备运行参数,比较历史数据,分析是否存在异常。 |
|
| 1 | 设备运行状况 | 检查设备运行是否平稳,有无异常振动或噪音。 | 详细记录设备运行参数,比较历史数据,分析是否存在异常。 |
|
||||||
| 2 | 安全防护措施 | 检查安全防护设施是否完好,如安全栅栏、警示标志等。 | 确认所有安全设施无损坏,位置正确,且工作人员了解如何正确使用。 |
|
| 2 | 安全防护措施 | 检查安全防护设施是否完好,如安全栅栏、警示标志等。 | 确认所有安全设施无损坏,位置正确,且工作人员了解如何正确使用。 |
|
||||||
| 3 | 环境卫生状况 | 检查工作区域是否清洁,有无垃圾或障碍物。 | 清理工作区域,确保无杂物,保持环境整洁。 |
|
| 3 | 环境卫生状况 | 检查工作区域是否清洁,有无垃圾或障碍物。 | 清理工作区域,确保无杂物,保持环境整洁。 |
|
||||||
|...|.....|....|...|
|
| ... | ..... | .... | ... |
|
||||||
|
|
||||||
## 第三部分:检查记录
|
## 第三部分:检查记录
|
||||||
|
|
||||||
| 序号 | 检查项目 | 检查结果 | 异常说明 | 处理措施 | 反馈意见 |
|
| 序号 | 检查项目 | 检查结果 | 异常说明 | 处理措施 | 反馈意见 |
|
||||||
| ---- | -------- | -------- | ---------------- | ------------------------------ | ---------------------- |
|
|-----|-------|------|----------|-----------------|-------------|
|
||||||
| 1 | 外壳 | 正常 | 无 | 无 | 无 |
|
| 1 | 外壳 | 正常 | 无 | 无 | 无 |
|
||||||
| 2 | 电源 | 异常 | 设备有轻微振动。 | 已联系维修人员,计划明日检修。 | 建议增加设备维护频率。 |
|
| 2 | 电源 | 异常 | 设备有轻微振动。 | 已联系维修人员,计划明日检修。 | 建议增加设备维护频率。 |
|
||||||
| 3 | 主机 | 正常 | 无 | 无 | 无 |
|
| 3 | 主机 | 正常 | 无 | 无 | 无 |
|
||||||
|...|.....|....|...|...|...|
|
| ... | ..... | .... | ... | ... | ... |
|
||||||
|
|
||||||
## 第四部分:巡检总结
|
## 第四部分:巡检总结
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
autoReload="true"
|
||||||
|
internalLogLevel="Info"
|
||||||
|
internalLogFile="c:\temp\internal-nlog-AspNetCore.txt">
|
||||||
|
|
||||||
|
<!-- 启用 ASP.NET Core 布局渲染器 -->
|
||||||
|
<extensions>
|
||||||
|
<add assembly="NLog.Web.AspNetCore"/>
|
||||||
|
</extensions>
|
||||||
|
|
||||||
|
<!-- 日志写入的目标 -->
|
||||||
|
<targets>
|
||||||
|
<!-- 所有日志消息的基础详细信息文件目标 -->
|
||||||
|
<target xsi:type="File" name="allfile" fileName="logs/all-${shortdate}.log"
|
||||||
|
layout="${longdate}|${event-properties:item=EventId:whenEmpty=0}|${level:uppercase=true}|${logger}|${message}${exception:format=tostring}" />
|
||||||
|
|
||||||
|
<!-- 自定义日志消息的额外Web详细信息文件目标 -->
|
||||||
|
<target xsi:type="File" name="ownFile-web" fileName="logs/own-${shortdate}.log"
|
||||||
|
layout="${longdate}|${event-properties:item=EventId:whenEmpty=0}|${level:uppercase=true}|${logger}|${message}${exception:format=tostring}|url: ${aspnet-request-url}|action:${aspnet-mvc-action}" />
|
||||||
|
|
||||||
|
<!-- 控制台目标,用于提高 Docker / Visual Studio 启动检测的速度 -->
|
||||||
|
<target xsi:type="Console" name="lifetimeConsole" layout="${MicrosoftConsoleLayout}" />
|
||||||
|
|
||||||
|
<!-- LoongPanel_Asp.Servers.SshService 日志的单独文件目标 -->
|
||||||
|
<target xsi:type="File" name="sshServiceFile" fileName="logs/ssh-${shortdate}.log"
|
||||||
|
layout="${longdate}|${event-properties:item=EventId:whenEmpty=0}|${level:uppercase=true}|${logger}|${message}${exception:format=tostring}" />
|
||||||
|
</targets>
|
||||||
|
|
||||||
|
<!-- 将日志记录器名称映射到目标的规则 -->
|
||||||
|
<rules>
|
||||||
|
<!-- 所有日志,包括来自 Microsoft 的 -->
|
||||||
|
<logger name="*" minlevel="Trace" writeTo="allfile" />
|
||||||
|
|
||||||
|
<!-- 将宿主生命周期消息输出到控制台目标,以便更快地启动检测 -->
|
||||||
|
<logger name="Microsoft.Hosting.Lifetime" minlevel="Info" writeTo="lifetimeConsole, ownFile-web" final="true" />
|
||||||
|
|
||||||
|
<!-- 忽略非关键的 Microsoft 日志,因此只记录自己的日志 -->
|
||||||
|
<logger name="Microsoft.*" maxlevel="Info" final="true" />
|
||||||
|
<logger name="System.Net.Http.*" maxlevel="Info" final="true" />
|
||||||
|
|
||||||
|
<!-- 将 LoongPanel_Asp.Servers.SshService 的日志消息写入单独的文件 -->
|
||||||
|
<logger name="LoongPanel_Asp.Servers.SshService" minlevel="Trace" writeTo="sshServiceFile" final="true" />
|
||||||
|
|
||||||
|
<logger name="*" minlevel="Trace" writeTo="ownFile-web" />
|
||||||
|
</rules>
|
||||||
|
</nlog>
|
|
@ -3,7 +3,6 @@ import {charts} from "~/config/charts";
|
||||||
import type {PropType} from "vue";
|
import type {PropType} from "vue";
|
||||||
import type {serverValueItem} from "~/components/SettingCard.vue";
|
import type {serverValueItem} from "~/components/SettingCard.vue";
|
||||||
import {useMainLayoutStore} from "~/strores/UseMainLayoutStore";
|
import {useMainLayoutStore} from "~/strores/UseMainLayoutStore";
|
||||||
import {THEME_KEY} from "vue-echarts";
|
|
||||||
|
|
||||||
|
|
||||||
const mainLayoutStore = useMainLayoutStore()
|
const mainLayoutStore = useMainLayoutStore()
|
||||||
|
@ -95,25 +94,15 @@ const items = [
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :id="`card_${id}`" ref="cardRef" class="card-layout ">
|
<div :id="`card_${id}`" ref="cardRef" class="card-layout ">
|
||||||
<Dialog v-model:visible="visible" :pt="{
|
|
||||||
root: 'border-none',
|
|
||||||
mask: {
|
|
||||||
style: 'backdrop-filter: blur(10px)'
|
|
||||||
}
|
|
||||||
}" modal>
|
|
||||||
<template #container="{ closeCallback }">
|
|
||||||
<SettingCard :card-id="id" :close-callback="closeCallback" is-setting/>
|
|
||||||
</template>
|
|
||||||
</Dialog>
|
|
||||||
<SplitButton :model="items" class="SplitButton"/>
|
|
||||||
<div :id="'item' + id" class="card-title vue-draggable-handle">
|
<div :id="'item' + id" class="card-title vue-draggable-handle">
|
||||||
<div></div>
|
|
||||||
<h3>{{ title ?? "默认标题" }}</h3>
|
<h3>{{ title ?? "默认标题" }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<component :is="charts.find(x => x.id === chart)?.component" v-if="targetIsVisible&&!isSwitched"
|
<div class="card-content">
|
||||||
:rangeNum="rangeNum"
|
<component :is="charts.find(x => x.id === chart)?.component" v-if="targetIsVisible&&!isSwitched"
|
||||||
:valueIds="valueIds"
|
:rangeNum="rangeNum"
|
||||||
:valueNames="valueNames"/>
|
:valueIds="valueIds"
|
||||||
|
:valueNames="valueNames"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -121,71 +110,31 @@ const items = [
|
||||||
@import "base";
|
@import "base";
|
||||||
|
|
||||||
.card-layout {
|
.card-layout {
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
background: $light-bg-color;
|
background: $light-bg-color;
|
||||||
border-radius: $radius;
|
display: grid;
|
||||||
box-shadow: 0 10px 30px 0 rgba(17, 38, 146, 0.05);
|
grid-template-rows: 40px 1fr;
|
||||||
padding: $padding*1.5;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $gap*1.5;
|
|
||||||
will-change: scroll-position, contents;
|
|
||||||
border: $border;
|
|
||||||
.dark-mode & {
|
.dark-mode & {
|
||||||
background: $dark-bg-color;
|
background: $dark-bg-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.SplitButton {
|
|
||||||
position: absolute;
|
|
||||||
right: $padding*.5;
|
|
||||||
top: $padding*.5;
|
|
||||||
|
|
||||||
:deep(.p-button) {
|
|
||||||
background: unset;
|
|
||||||
border: unset;
|
|
||||||
padding: unset;
|
|
||||||
|
|
||||||
> svg {
|
|
||||||
stroke: $light-unfocused-color;
|
|
||||||
|
|
||||||
.dark-mode & {
|
|
||||||
stroke: $dark-unfocused-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
stroke: $light-text-color;
|
|
||||||
|
|
||||||
.dark-mode & {
|
|
||||||
stroke: $dark-text-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: $gap;
|
padding: 0 20px;
|
||||||
color: $light-text-color;
|
|
||||||
|
h3 {
|
||||||
&,
|
font-size: 14px;
|
||||||
h3:hover {
|
line-height: 24px;
|
||||||
cursor: move;
|
text-align: left;
|
||||||
}
|
color: #333;
|
||||||
|
|
||||||
.dark-mode & {
|
|
||||||
color: $dark-text-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
div {
|
|
||||||
width: 6px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: $radius;
|
|
||||||
background: $primary-color;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.card-content{
|
||||||
|
padding: 0 20px 20px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -7,10 +7,6 @@ const props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: "CPU使用率",
|
default: "CPU使用率",
|
||||||
},
|
},
|
||||||
info:{
|
|
||||||
type:Array,
|
|
||||||
default:()=>[],
|
|
||||||
},
|
|
||||||
unit: {
|
unit: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "%"
|
default: "%"
|
||||||
|
@ -50,7 +46,7 @@ dataStore.$subscribe((_, state) => {
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<div class="mini-card-box">
|
<div class="mini-card-box">
|
||||||
<n-progress type="circle" processing :status="status" :percentage="
|
<n-progress type="circle" processing :status="status" :percentage="
|
||||||
values[0]
|
Number(values[0])
|
||||||
" style="width: 68px;">
|
" style="width: 68px;">
|
||||||
<MoveDown class="arrow" :style="{transform:`rotate(${(100-value)/100*360*-1}deg)`}" v-if="status==='success'"/>
|
<MoveDown class="arrow" :style="{transform:`rotate(${(100-value)/100*360*-1}deg)`}" v-if="status==='success'"/>
|
||||||
<div class="warning" v-if="status==='warning'">
|
<div class="warning" v-if="status==='warning'">
|
||||||
|
@ -70,9 +66,9 @@ dataStore.$subscribe((_, state) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<n-tag type="info" v-for="i in info">
|
<slot>
|
||||||
{{ i }}
|
这里什么都没有
|
||||||
</n-tag>
|
</slot>
|
||||||
</n-popover>
|
</n-popover>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -4,17 +4,14 @@ import {useMainLayoutStore} from "~/strores/UseMainLayoutStore";
|
||||||
|
|
||||||
import type {PropType} from "vue";
|
import type {PropType} from "vue";
|
||||||
import {ref} from 'vue';
|
import {ref} from 'vue';
|
||||||
import dayjs from "dayjs";
|
|
||||||
import VChart from 'vue-echarts';
|
import VChart from 'vue-echarts';
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import {useSessionSignalRStore} from "~/strores/HubStore";
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
|
const hubStore = useSessionSignalRStore()
|
||||||
const mainLayoutStore = useMainLayoutStore()
|
const mainLayoutStore = useMainLayoutStore()
|
||||||
type ArbitraryKeyValuePairs = {
|
const chartRef = ref<any>(null)
|
||||||
[key: string]: (number | string)[];
|
|
||||||
};
|
|
||||||
const values = ref<ArbitraryKeyValuePairs>({});
|
|
||||||
const chartRet = ref<any>(null)
|
|
||||||
const isLoading = ref<boolean>(true)
|
const isLoading = ref<boolean>(true)
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
valueIds: {
|
valueIds: {
|
||||||
|
@ -32,7 +29,9 @@ const props = defineProps({
|
||||||
})
|
})
|
||||||
const option = computed(() => {
|
const option = computed(() => {
|
||||||
return {
|
return {
|
||||||
backgroundColor:'',
|
backgroundColor: '',
|
||||||
|
//关闭动画
|
||||||
|
// animation: false,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
|
@ -43,47 +42,39 @@ const option = computed(() => {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
left: '2%',
|
left: '0',
|
||||||
right: '50',
|
right: '50',
|
||||||
bottom: '10%',
|
bottom: '38',
|
||||||
|
top: '15',
|
||||||
containLabel: true
|
containLabel: true
|
||||||
},
|
},
|
||||||
xAxis: [
|
xAxis: [
|
||||||
{
|
{
|
||||||
type: 'category',
|
type: 'time',
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
data: []
|
axisLabel: {
|
||||||
}
|
showMinLabel: true,
|
||||||
|
showMaxLabel: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
],
|
],
|
||||||
yAxis: [
|
yAxis: [
|
||||||
{
|
{
|
||||||
type: 'value',
|
type: 'value',
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
toolbox: {
|
|
||||||
feature: {
|
|
||||||
dataZoom: {
|
|
||||||
yAxisIndex: 'none'
|
|
||||||
},
|
|
||||||
|
|
||||||
saveAsImage: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dataZoom: [
|
dataZoom: [
|
||||||
{
|
{
|
||||||
type: 'inside',
|
type: 'inside',
|
||||||
start: 90,
|
start: 99.5,
|
||||||
end: 100,
|
end: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'slider',
|
type: 'slider',
|
||||||
throttle: 500,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'slider',
|
type: 'slider',
|
||||||
yAxisIndex: 0,
|
yAxisIndex: 0,
|
||||||
startValue: 0,
|
|
||||||
endValue: 100,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
legend: {
|
legend: {
|
||||||
|
@ -96,9 +87,20 @@ const option = computed(() => {
|
||||||
areaStyle: {},
|
areaStyle: {},
|
||||||
large: true,
|
large: true,
|
||||||
smooth: true,
|
smooth: true,
|
||||||
|
largeThreshold: 10000,
|
||||||
|
sampling: 'lttb',
|
||||||
emphasis: {
|
emphasis: {
|
||||||
focus: 'series'
|
focus: 'series'
|
||||||
},
|
},
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{type: 'max', name: 'Max'},
|
||||||
|
{type: 'min', name: 'Min'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
markLine: {
|
||||||
|
data: [{type: 'average', name: 'Avg'}]
|
||||||
|
},
|
||||||
data: [],
|
data: [],
|
||||||
}
|
}
|
||||||
}) ?? []
|
}) ?? []
|
||||||
|
@ -108,59 +110,34 @@ let interval: NodeJS.Timeout;
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
})
|
})
|
||||||
|
let maxValue = 100;
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
let history = dataStore.dataHistory
|
if (!chartRef.value) {
|
||||||
setTimeout(() => {
|
console.error('ECharts instance is not initialized');
|
||||||
isLoading.value = false;
|
return;
|
||||||
}, 5000)
|
}
|
||||||
nextTick(() => {
|
hubStore.connection?.on("ReceiveDataHistory", (type: string, values: [number,string][], done: boolean) => {
|
||||||
props.valueIds?.forEach((id, index) => {
|
chartRef.value.appendData({
|
||||||
chartRet.value.appendData({
|
seriesIndex: props.valueIds?.indexOf(type),
|
||||||
seriesIndex: index,
|
data: values
|
||||||
data: history.data[id]
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
const currentOption = chartRet.value.getOption();
|
if (done) {
|
||||||
currentOption.xAxis[0].data = history.times
|
//设置y轴最大值为数据最大值的140%
|
||||||
chartRet.value.setOption(currentOption)
|
// const currentOption = chartRef.value.getOption();
|
||||||
isLoading.value = false
|
// const maxData = Math.max(...values.map(item =>item[1]));
|
||||||
interval = setInterval(() => {
|
|
||||||
const data = dataStore.data
|
// if (maxData > maxValue) {
|
||||||
props.valueIds?.forEach((id, index) => {
|
// maxValue = maxData
|
||||||
const newData = data[id] ?? 0
|
// currentOption.yAxis[0].max = maxData * 1.4;
|
||||||
if (!values.value[id]) {
|
// chartRef.value.setOption(currentOption);
|
||||||
values.value[id] = []
|
// }
|
||||||
}
|
|
||||||
chartRet.value.appendData({
|
}
|
||||||
seriesIndex: index,
|
isLoading.value = false;
|
||||||
data: [newData]
|
|
||||||
})
|
|
||||||
})
|
|
||||||
const currentOption = chartRet.value.getOption();
|
|
||||||
currentOption.xAxis[0].data.push(dayjs().format('MM-DD HH:mm:ss'))
|
|
||||||
chartRet.value.setOption(currentOption)
|
|
||||||
}, 4000 + 1000 * Math.random())
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
let endIndex = dataStore.dataHistory.times ? dataStore.dataHistory.times.length : 0
|
|
||||||
let startTemp = 0;
|
setTimeout(() => {
|
||||||
let done = false;
|
|
||||||
type Data = {
|
|
||||||
times: string[]; // 时间轴
|
|
||||||
data: { [key: string]: string[] }; // 数据,键是字符串,值是字符串数组的数组
|
|
||||||
endIndex: number; // 结束索引
|
|
||||||
done: boolean; // 是否完成加载
|
|
||||||
};
|
|
||||||
const zoom = _.throttle((e: any) => {
|
|
||||||
console.log(e)
|
|
||||||
let start = e.start ?? e.batch[0].start
|
|
||||||
if (done) return
|
|
||||||
if (start <= 50 && start !== startTemp) {
|
|
||||||
startTemp = e.start
|
|
||||||
isLoading.value = true
|
|
||||||
setTimeout(() => {
|
|
||||||
isLoading.value = false
|
|
||||||
}, 5000)
|
|
||||||
$fetch('/Api/Server/GetServerHistoryDate', {
|
$fetch('/Api/Server/GetServerHistoryDate', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -169,41 +146,75 @@ const zoom = _.throttle((e: any) => {
|
||||||
params: {
|
params: {
|
||||||
ServerId: mainLayoutStore.SelectServer.value,
|
ServerId: mainLayoutStore.SelectServer.value,
|
||||||
DataTypes: props.valueIds,
|
DataTypes: props.valueIds,
|
||||||
StartIndex: endIndex,
|
StartIndex: 0,
|
||||||
},
|
},
|
||||||
baseURL: useRuntimeConfig().public.baseUrl,
|
baseURL: useRuntimeConfig().public.baseUrl,
|
||||||
}).then((res) => {
|
|
||||||
console.log(res)
|
|
||||||
const data = res as Data;
|
|
||||||
if (data.done) {
|
|
||||||
isLoading.value = false
|
|
||||||
done = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
endIndex = data.endIndex
|
|
||||||
//获得图表data
|
|
||||||
const currentOption = chartRet.value.getOption();
|
|
||||||
currentOption.series.map((series: any, index: number) => {
|
|
||||||
series.data = [...data.data[props.valueIds[index]], ...series.data]
|
|
||||||
})
|
|
||||||
currentOption.xAxis[0].data = [...data.times, ...currentOption.xAxis[0].data]
|
|
||||||
//start 为5
|
|
||||||
currentOption.dataZoom[0].start = 55
|
|
||||||
chartRet.value.setOption(currentOption)
|
|
||||||
setTimeout(() => {
|
|
||||||
isLoading.value = false
|
|
||||||
}, 1000)
|
|
||||||
})
|
})
|
||||||
}
|
}, 2000)
|
||||||
}, 1000)
|
|
||||||
|
})
|
||||||
|
// let endIndex = dataStore.dataHistory.times ? dataStore.dataHistory.times.length : 0
|
||||||
|
// let startTemp = 0;
|
||||||
|
// let done = false;
|
||||||
|
// type Data = {
|
||||||
|
// times: string[]; // 时间轴
|
||||||
|
// data: { [key: string]: string[] }; // 数据,键是字符串,值是字符串数组的数组
|
||||||
|
// endIndex: number; // 结束索引
|
||||||
|
// done: boolean; // 是否完成加载
|
||||||
|
// };
|
||||||
|
// const zoom = _.throttle((e: any) => {
|
||||||
|
// console.log(e)
|
||||||
|
// let start = e.start ?? e.batch[0].start
|
||||||
|
// if (done) return
|
||||||
|
// if (start <= 50 && start !== startTemp) {
|
||||||
|
// startTemp = e.start
|
||||||
|
// isLoading.value = true
|
||||||
|
// setTimeout(() => {
|
||||||
|
// isLoading.value = false
|
||||||
|
// }, 5000)
|
||||||
|
// $fetch('/Api/Server/GetServerHistoryDate', {
|
||||||
|
// method: 'GET',
|
||||||
|
// headers: {
|
||||||
|
// 'Authorization': 'Bearer ' + useCookie('token').value
|
||||||
|
// },
|
||||||
|
// params: {
|
||||||
|
// ServerId: mainLayoutStore.SelectServer.value,
|
||||||
|
// DataTypes: props.valueIds,
|
||||||
|
// StartIndex: endIndex,
|
||||||
|
// },
|
||||||
|
// baseURL: useRuntimeConfig().public.baseUrl,
|
||||||
|
// }).then((res) => {
|
||||||
|
// console.log(res)
|
||||||
|
// const data = res as Data;
|
||||||
|
// if (data.done) {
|
||||||
|
// isLoading.value = false
|
||||||
|
// done = true
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// endIndex = data.endIndex
|
||||||
|
// //获得图表data
|
||||||
|
// const currentOption = chartRet.value.getOption();
|
||||||
|
// currentOption.series.map((series: any, index: number) => {
|
||||||
|
// series.data = [...data.data[props.valueIds[index]], ...series.data]
|
||||||
|
// })
|
||||||
|
// currentOption.xAxis[0].data = [...data.times, ...currentOption.xAxis[0].data]
|
||||||
|
// //start 为5
|
||||||
|
// currentOption.dataZoom[0].start = 55
|
||||||
|
// chartRet.value.setOption(currentOption)
|
||||||
|
// setTimeout(() => {
|
||||||
|
// isLoading.value = false
|
||||||
|
// }, 1000)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }, 1000)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-chart ref="chartRet" :loading="isLoading" :manual-update="true" :option="option"
|
<v-chart ref="chartRef" :loading="isLoading" :manual-update="true" :option="option"
|
||||||
autoresize
|
autoresize
|
||||||
:theme="$colorMode.value"
|
:theme="$colorMode.value"
|
||||||
class="chart"
|
class="chart"
|
||||||
@datazoom="zoom"/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -60,15 +60,6 @@ const option = computed(() => {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
toolbox: {
|
|
||||||
feature: {
|
|
||||||
dataZoom: {
|
|
||||||
yAxisIndex: 'none'
|
|
||||||
},
|
|
||||||
|
|
||||||
saveAsImage: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dataZoom: [
|
dataZoom: [
|
||||||
{
|
{
|
||||||
type: 'inside',
|
type: 'inside',
|
||||||
|
@ -88,6 +79,8 @@ const option = computed(() => {
|
||||||
],
|
],
|
||||||
legend: {
|
legend: {
|
||||||
data: props.valueNames,
|
data: props.valueNames,
|
||||||
|
right:"5",
|
||||||
|
top:"1",
|
||||||
textStyle: {
|
textStyle: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
},
|
},
|
||||||
|
@ -101,6 +94,15 @@ const option = computed(() => {
|
||||||
emphasis: {
|
emphasis: {
|
||||||
focus: 'series'
|
focus: 'series'
|
||||||
},
|
},
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{ type: 'max', name: 'Max' },
|
||||||
|
{ type: 'min', name: 'Min' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
markLine: {
|
||||||
|
data: [{ type: 'average', name: 'Avg' }]
|
||||||
|
},
|
||||||
data: [],
|
data: [],
|
||||||
}
|
}
|
||||||
}) ?? []
|
}) ?? []
|
||||||
|
|
|
@ -108,6 +108,9 @@ watchDebounced(
|
||||||
<n-modal
|
<n-modal
|
||||||
v-model:show="visible"
|
v-model:show="visible"
|
||||||
style="width: auto;"
|
style="width: auto;"
|
||||||
|
preset="card"
|
||||||
|
title="添加图表"
|
||||||
|
|
||||||
>
|
>
|
||||||
<SettingCard />
|
<SettingCard />
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
|
|
@ -10,6 +10,14 @@ import _ from "lodash";
|
||||||
|
|
||||||
var term: Terminal;
|
var term: Terminal;
|
||||||
const terminal = ref<HTMLDivElement|null>(null);
|
const terminal = ref<HTMLDivElement|null>(null);
|
||||||
|
const props=defineProps(
|
||||||
|
{
|
||||||
|
isPage:{
|
||||||
|
type:Boolean,
|
||||||
|
default:false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
const resizeObserver =_.debounce(()=>{
|
const resizeObserver =_.debounce(()=>{
|
||||||
if(terminal.value===null) return;
|
if(terminal.value===null) return;
|
||||||
const cols = Math.floor(terminal.value.clientWidth / 9);
|
const cols = Math.floor(terminal.value.clientWidth / 9);
|
||||||
|
@ -24,8 +32,11 @@ onMounted(()=>{
|
||||||
}});
|
}});
|
||||||
term.open(document.getElementById("terminal") as HTMLElement);
|
term.open(document.getElementById("terminal") as HTMLElement);
|
||||||
if(terminal.value===null) return;
|
if(terminal.value===null) return;
|
||||||
const cols = Math.floor(terminal.value.clientWidth / 9);
|
let cols = Math.floor(terminal.value.clientWidth / 9);
|
||||||
const rows = Math.floor(terminal.value.clientHeight / 17);
|
let rows = Math.floor(terminal.value.clientHeight / 17);
|
||||||
|
if (props.isPage) {
|
||||||
|
cols-=1
|
||||||
|
}
|
||||||
term.resize(cols-2, rows-1)
|
term.resize(cols-2, rows-1)
|
||||||
connection.invoke("ResizeTerminal", rows-1, cols-2)
|
connection.invoke("ResizeTerminal", rows-1, cols-2)
|
||||||
term.focus();
|
term.focus();
|
||||||
|
@ -64,9 +75,9 @@ onMounted(async () => {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("Terminal created")
|
console.log("Terminal created")
|
||||||
})
|
})
|
||||||
setTimeout(()=>{
|
// setTimeout(()=>{
|
||||||
connection.invoke("SendMessage","neofetch\n")
|
// connection.invoke("SendMessage","neofetch\n")
|
||||||
},200)
|
// },200)
|
||||||
})
|
})
|
||||||
const resize=()=>{
|
const resize=()=>{
|
||||||
|
|
||||||
|
@ -80,7 +91,9 @@ const resize=()=>{
|
||||||
let width = x - terminal.value.getBoundingClientRect().left;
|
let width = x - terminal.value.getBoundingClientRect().left;
|
||||||
let height = y - terminal.value.getBoundingClientRect().top;
|
let height = y - terminal.value.getBoundingClientRect().top;
|
||||||
// 设置terminal的宽高
|
// 设置terminal的宽高
|
||||||
terminal.value.style.width = `${width}px`;
|
if(!props.isPage){
|
||||||
|
terminal.value.style.width = `${width}px`;
|
||||||
|
}
|
||||||
terminal.value.style.height = `${height}px`;
|
terminal.value.style.height = `${height}px`;
|
||||||
}
|
}
|
||||||
document.addEventListener('mousemove', onMouseMove, { passive: true });
|
document.addEventListener('mousemove', onMouseMove, { passive: true });
|
||||||
|
@ -91,7 +104,9 @@ const resize=()=>{
|
||||||
width = Math.round(width / 9) * 9+20;
|
width = Math.round(width / 9) * 9+20;
|
||||||
let height = terminal.value.getBoundingClientRect().height;
|
let height = terminal.value.getBoundingClientRect().height;
|
||||||
height = Math.round(height / 17) * 17+20;
|
height = Math.round(height / 17) * 17+20;
|
||||||
terminal.value.style.width = `${width}px`;
|
if(!props.isPage){
|
||||||
|
terminal.value.style.width = `${width}px`;
|
||||||
|
}
|
||||||
terminal.value.style.height = `${height}px`;
|
terminal.value.style.height = `${height}px`;
|
||||||
resizeObserver()
|
resizeObserver()
|
||||||
// 停止监听鼠标移动事件
|
// 停止监听鼠标移动事件
|
||||||
|
@ -107,10 +122,29 @@ const resize=()=>{
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="terminal-box" ref="terminal">
|
<div :class="{'terminal-box':true,'terminal-box-page':isPage}" ref="terminal">
|
||||||
<div id="terminal" @resize="resizeObserver" >
|
<div id="terminal" @resize="resizeObserver" >
|
||||||
</div>
|
</div>
|
||||||
<ArrowDownRight class="arrow" @mousedown="resize" />
|
<ArrowDownRight class="arrow" @mousedown="resize" />
|
||||||
|
<svg
|
||||||
|
class="bg2"
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
style="background: black;filter:grayscale(1);"
|
||||||
|
>
|
||||||
|
|
||||||
|
<filter id='noiseFilter'>
|
||||||
|
<feTurbulence
|
||||||
|
type='fractalNoise'
|
||||||
|
baseFrequency='0.65'
|
||||||
|
numOctaves='3'
|
||||||
|
stitchTiles='stitch' />
|
||||||
|
</filter>
|
||||||
|
|
||||||
|
<rect
|
||||||
|
width='100%'
|
||||||
|
height='100%'
|
||||||
|
filter='url(#noiseFilter)' />
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@ -125,11 +159,29 @@ const resize=()=>{
|
||||||
height: 500px;
|
height: 500px;
|
||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
border-radius: $radius;
|
min-width: 400px;
|
||||||
background: rgba(0,0,0,.5);
|
min-height: 300px;
|
||||||
backdrop-filter: blur(20px)
|
border-radius: $radius*2;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 2px solid #fff;
|
||||||
|
will-change: contents;
|
||||||
|
}
|
||||||
|
.terminal-box-page{
|
||||||
|
border: unset;
|
||||||
|
padding: unset;
|
||||||
|
width: 100%;
|
||||||
|
max-width: unset;
|
||||||
|
}
|
||||||
|
.bg2{
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#terminal {
|
#terminal {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&::-webkit-scrollbar{
|
&::-webkit-scrollbar{
|
||||||
|
|
|
@ -25,7 +25,6 @@ onMounted(() => {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 32px;
|
padding: 32px;
|
||||||
gap: 32px;
|
gap: 32px;
|
||||||
background: unset;
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr minmax(800px,1fr);
|
grid-template-columns: 1fr minmax(800px,1fr);
|
||||||
grid-template-rows: 1fr;
|
grid-template-rows: 1fr;
|
||||||
|
|
|
@ -7,8 +7,7 @@ import type {UserInfoType} from "~/types/UserType";
|
||||||
import type {HttpType} from "~/types/baseType";
|
import type {HttpType} from "~/types/baseType";
|
||||||
import {useSessionSignalRStore} from "~/strores/HubStore";
|
import {useSessionSignalRStore} from "~/strores/HubStore";
|
||||||
import {POSITION, useToast} from "vue-toastification";
|
import {POSITION, useToast} from "vue-toastification";
|
||||||
import {type dataHistoryType, useDataStore} from "~/strores/DataStore";
|
import { useDataStore} from "~/strores/DataStore";
|
||||||
import VueDragResize from 'vue-drag-resize'
|
|
||||||
|
|
||||||
const audio = ref<any>(null);
|
const audio = ref<any>(null);
|
||||||
const audio1 = ref<any>(null);
|
const audio1 = ref<any>(null);
|
||||||
|
@ -16,7 +15,6 @@ const audio2 = ref<any>(null);
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const visible = ref<boolean>(false)
|
const visible = ref<boolean>(false)
|
||||||
const DataStore = useDataStore()
|
const DataStore = useDataStore()
|
||||||
const {$gsap} = useNuxtApp()
|
|
||||||
const mainRef = ref<HTMLElement>()
|
const mainRef = ref<HTMLElement>()
|
||||||
const mainLayoutStore = useMainLayoutStore()
|
const mainLayoutStore = useMainLayoutStore()
|
||||||
const ServerList = ref<{ label: string, value: string }[]>([])
|
const ServerList = ref<{ label: string, value: string }[]>([])
|
||||||
|
@ -132,40 +130,9 @@ onMounted(() => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
//断开链接
|
|
||||||
signalR.connection?.stop()
|
signalR.connection?.stop()
|
||||||
})
|
})
|
||||||
const getHistoryData = async () => {
|
|
||||||
$fetch('/Api/Server/GetServerHistoryDate', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + useCookie('token').value
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
ServerId: mainLayoutStore.SelectServer.value,
|
|
||||||
},
|
|
||||||
baseURL: useRuntimeConfig().public.baseUrl,
|
|
||||||
}).then((res) => {
|
|
||||||
const data = res as dataHistoryType
|
|
||||||
DataStore.dataHistory = data
|
|
||||||
const datas = data.data
|
|
||||||
//遍历字典
|
|
||||||
for (const key in datas) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(datas, key)) {
|
|
||||||
const element = datas[key];
|
|
||||||
//获取最后一次的数据
|
|
||||||
DataStore.data[key] = element[element.length - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataStore.startTimer()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onMounted(async () => {
|
|
||||||
await getHistoryData()
|
|
||||||
})
|
|
||||||
watch(() => mainLayoutStore.SelectServer.value, async () => {
|
|
||||||
await getHistoryData()
|
|
||||||
})
|
|
||||||
let isShift = false
|
let isShift = false
|
||||||
onKeyStroke('Shift', (e) => {
|
onKeyStroke('Shift', (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -199,7 +166,6 @@ onKeyStroke('Shift', (e) => {
|
||||||
<SideBar/>
|
<SideBar/>
|
||||||
<TitleBar/>
|
<TitleBar/>
|
||||||
<n-modal v-model:show.lazy="visible" auto-focus >
|
<n-modal v-model:show.lazy="visible" auto-focus >
|
||||||
|
|
||||||
<Term/>
|
<Term/>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
<div class="main-box">
|
<div class="main-box">
|
||||||
|
|
|
@ -68,7 +68,8 @@ export default defineNuxtConfig({
|
||||||
css: ['assets/min.scss', 'vue-toastification/dist/index.css'],
|
css: ['assets/min.scss', 'vue-toastification/dist/index.css'],
|
||||||
|
|
||||||
devServer: {
|
devServer: {
|
||||||
port: 3001, host: '0.0.0.0',
|
port: 3001,
|
||||||
|
// host: '0.0.0.0',
|
||||||
// https: {
|
// https: {
|
||||||
// key: "./localhost+3-key.pem",
|
// key: "./localhost+3-key.pem",
|
||||||
// cert: "./localhost+3.pem",
|
// cert: "./localhost+3.pem",
|
||||||
|
|
|
@ -8,7 +8,7 @@ definePageMeta({
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="terminal-layout">
|
<div class="terminal-layout">
|
||||||
<Term/>
|
<Term is-page/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -20,7 +20,8 @@ definePageMeta({
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
border: $border;
|
border: $border;
|
||||||
background: #000;
|
background: #000;
|
||||||
padding: $padding*2;
|
padding: $padding;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,14 @@ import {
|
||||||
CalendarComponent,
|
CalendarComponent,
|
||||||
AriaComponent,
|
AriaComponent,
|
||||||
BrushComponent,
|
BrushComponent,
|
||||||
|
MarkLineComponent,
|
||||||
|
MarkPointComponent,
|
||||||
TitleComponent,
|
TitleComponent,
|
||||||
LegendComponent,
|
LegendComponent,
|
||||||
DataZoomComponent
|
DataZoomComponent
|
||||||
} from 'echarts/components';
|
} from 'echarts/components';
|
||||||
|
|
||||||
export default defineNuxtPlugin(() => {
|
export default defineNuxtPlugin(() => {
|
||||||
use([CanvasRenderer, BarChart, GaugeChart, LinesChart, LineChart, PieChart, GridComponent, TooltipComponent, ToolboxComponent, TimelineComponent, TitleComponent, LegendComponent, DataZoomComponent]);
|
use([CanvasRenderer, BarChart, GaugeChart, LinesChart, LineChart, PieChart, GridComponent, TooltipComponent, ToolboxComponent, TimelineComponent, TitleComponent, LegendComponent, DataZoomComponent,MarkLineComponent,
|
||||||
|
MarkPointComponent]);
|
||||||
});
|
});
|
|
@ -5,42 +5,11 @@ import dayjs from "dayjs";
|
||||||
export const useDataStore = defineStore('DataStore', {
|
export const useDataStore = defineStore('DataStore', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
data: <dataType>{},
|
data: <dataType>{},
|
||||||
dataHistory: <dataHistoryType>{...defaultDataHistory},
|
|
||||||
timer: null as any,
|
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
setData(type: string, data: any) {
|
setData(type: string, data: any) {
|
||||||
//不存在则添加,存在则更新
|
|
||||||
this.data[type] = data
|
this.data[type] = data
|
||||||
},
|
},
|
||||||
startTimer() {
|
|
||||||
if (this.timer) return; // 防止重复启动定时器
|
|
||||||
|
|
||||||
this.timer = setInterval(() => {
|
|
||||||
// 每秒将 data 中的数据添加到 dataHistory 中
|
|
||||||
this.addDataToHistory();
|
|
||||||
}, 5000);
|
|
||||||
},
|
|
||||||
stopTimer() {
|
|
||||||
if (this.timer) {
|
|
||||||
clearInterval(this.timer);
|
|
||||||
this.timer = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addDataToHistory() {
|
|
||||||
const currentTime = dayjs().format('MM-DD HH:mm:ss');
|
|
||||||
for (const key in this.data) {
|
|
||||||
const value = this.data[key];
|
|
||||||
if (!this.dataHistory.data[key]) {
|
|
||||||
this.dataHistory.data[key] = [];
|
|
||||||
}
|
|
||||||
this.dataHistory.data[key].push(value)
|
|
||||||
}
|
|
||||||
this.dataHistory.times.push(currentTime);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
persist: {
|
|
||||||
storage: persistedState.sessionStorage,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -48,15 +17,3 @@ export const useDataStore = defineStore('DataStore', {
|
||||||
export type dataType = {
|
export type dataType = {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type dataHistoryType = {
|
|
||||||
times: string[],
|
|
||||||
data: dataHistoryDataType,
|
|
||||||
}
|
|
||||||
type dataHistoryDataType = {
|
|
||||||
[key: string]: string[]
|
|
||||||
}
|
|
||||||
const defaultDataHistory: dataHistoryType = {
|
|
||||||
times: [],
|
|
||||||
data: {}
|
|
||||||
}
|
|
|
@ -44,7 +44,6 @@ export function useBeep() {
|
||||||
|
|
||||||
export function toggleDark(event: MouseEvent) {
|
export function toggleDark(event: MouseEvent) {
|
||||||
const isAppearanceTransition = document.startViewTransition && !window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
const isAppearanceTransition = document.startViewTransition && !window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||||
|
|
||||||
if (!isAppearanceTransition) {
|
if (!isAppearanceTransition) {
|
||||||
useColorMode().preference = useColorMode().value == 'dark' ? 'light' : 'dark'
|
useColorMode().preference = useColorMode().value == 'dark' ? 'light' : 'dark'
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in New Issue