mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
Implement wake-on-lan.
This commit is contained in:
parent
e6cc771b00
commit
1794059822
@ -36,6 +36,7 @@ namespace Remotely.Agent.Services
|
||||
|
||||
private readonly IDeviceInformationService _deviceInfoService;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly IWakeOnLanService _wakeOnLanService;
|
||||
private readonly ILogger<AgentHubConnection> _logger;
|
||||
private readonly ILogger _fileLogger;
|
||||
private readonly ScriptExecutor _scriptExecutor;
|
||||
@ -57,6 +58,7 @@ namespace Remotely.Agent.Services
|
||||
IUpdater updater,
|
||||
IDeviceInformationService deviceInfoService,
|
||||
IHttpClientFactory httpFactory,
|
||||
IWakeOnLanService wakeOnLanService,
|
||||
IEnumerable<ILoggerProvider> loggerProviders,
|
||||
ILogger<AgentHubConnection> logger)
|
||||
{
|
||||
@ -68,6 +70,7 @@ namespace Remotely.Agent.Services
|
||||
_updater = updater;
|
||||
_deviceInfoService = deviceInfoService;
|
||||
_httpFactory = httpFactory;
|
||||
_wakeOnLanService = wakeOnLanService;
|
||||
_logger = logger;
|
||||
_fileLogger = loggerProviders
|
||||
.OfType<FileLoggerProvider>()
|
||||
@ -489,6 +492,11 @@ namespace Remotely.Agent.Services
|
||||
});
|
||||
|
||||
_hubConnection.On("TriggerHeartbeat", SendHeartbeat);
|
||||
|
||||
_hubConnection.On("WakeDevice", async (string macAddress) =>
|
||||
{
|
||||
await _wakeOnLanService.WakeDevice(macAddress);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<bool> VerifyServer()
|
||||
|
||||
@ -3,8 +3,11 @@ using Remotely.Shared.Models;
|
||||
using Remotely.Shared.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Remotely.Agent.Services
|
||||
@ -31,6 +34,7 @@ namespace Remotely.Agent.Services
|
||||
OSDescription = RuntimeInformation.OSDescription,
|
||||
Is64Bit = Environment.Is64BitOperatingSystem,
|
||||
IsOnline = true,
|
||||
MacAddresses = GetMacAddresses().ToArray(),
|
||||
OrganizationID = orgID,
|
||||
AgentVersion = AppVersionHelper.GetAppVersion()
|
||||
};
|
||||
@ -95,5 +99,45 @@ namespace Remotely.Agent.Services
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetMacAddresses()
|
||||
{
|
||||
var macAddress = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
var nics = NetworkInterface.GetAllNetworkInterfaces();
|
||||
|
||||
if (!nics.Any())
|
||||
{
|
||||
return macAddress;
|
||||
}
|
||||
|
||||
var onlineNics = nics
|
||||
.Where(c =>
|
||||
c.NetworkInterfaceType != NetworkInterfaceType.Loopback &&
|
||||
c.OperationalStatus == OperationalStatus.Up);
|
||||
|
||||
foreach (var adapter in onlineNics)
|
||||
{
|
||||
var ipProperties = adapter.GetIPProperties();
|
||||
|
||||
var unicastAddresses = ipProperties.UnicastAddresses;
|
||||
if (!unicastAddresses.Any(temp => temp.Address.AddressFamily == AddressFamily.InterNetwork))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var address = adapter.GetPhysicalAddress();
|
||||
macAddress.Add(address.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while getting MAC addresses.");
|
||||
}
|
||||
|
||||
return macAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
39
Agent/Services/WakeOnLanService.cs
Normal file
39
Agent/Services/WakeOnLanService.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Agent.Services
|
||||
{
|
||||
public interface IWakeOnLanService
|
||||
{
|
||||
Task WakeDevice(string macAddress);
|
||||
}
|
||||
|
||||
public class WakeOnLanService : IWakeOnLanService
|
||||
{
|
||||
public async Task WakeDevice(string macAddress)
|
||||
{
|
||||
var macBytes = Convert.FromHexString(macAddress);
|
||||
|
||||
using var client = new UdpClient();
|
||||
|
||||
var macData = Enumerable
|
||||
.Repeat(macBytes, 16)
|
||||
.SelectMany(x => x);
|
||||
|
||||
var packet = Enumerable
|
||||
.Repeat((byte)0xFF, 6)
|
||||
.Concat(macData)
|
||||
.ToArray();
|
||||
|
||||
var broadcastAddress = System.Net.IPAddress.Parse("255.255.255.255");
|
||||
// WOL usually uses port 9.
|
||||
var endpoint = new IPEndPoint(broadcastAddress, 9);
|
||||
await client.SendAsync(packet, packet.Length, endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -154,14 +154,6 @@ namespace Remotely.Server.API
|
||||
var bannedDevices = _serviceSessionCache.GetAllDevices().Where(x => x.PublicIP == deviceIp);
|
||||
var connectionIds = _serviceSessionCache.GetConnectionIdsByDeviceIds(bannedDevices.Select(x => x.ID));
|
||||
|
||||
// TODO: Remove when devices have been removed.
|
||||
var command = "sc delete Remotely_Service & taskkill /im Remotely_Agent.exe /f";
|
||||
await _agentHubContext.Clients.Clients(connectionIds).SendAsync("ExecuteCommand",
|
||||
"cmd",
|
||||
command,
|
||||
Guid.NewGuid().ToString(),
|
||||
Guid.NewGuid().ToString());
|
||||
|
||||
await _agentHubContext.Clients.Clients(connectionIds).SendAsync("UninstallAgent");
|
||||
|
||||
return true;
|
||||
|
||||
@ -96,6 +96,12 @@
|
||||
<span class="ml-2">View Only</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="dropdown-item" @onclick="() => WakeDevice()">
|
||||
<i class="oi oi-eye" title="Wake Device"></i>
|
||||
<span class="ml-2">Wake</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<FileInputButton ClassNames="dropdown-item btn btn-primary"
|
||||
OnChanged="OnFileInputChanged">
|
||||
|
||||
@ -26,9 +26,8 @@ namespace Remotely.Server.Components.Devices
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, double> _fileUploadProgressLookup = new();
|
||||
private ElementReference _card;
|
||||
private Theme _theme;
|
||||
private Version _currentVersion = new();
|
||||
|
||||
private Theme _theme;
|
||||
[Parameter]
|
||||
public Device Device { get; set; }
|
||||
|
||||
@ -42,12 +41,6 @@ namespace Remotely.Server.Components.Devices
|
||||
[Inject]
|
||||
private ICircuitConnection CircuitConnection { get; set; }
|
||||
|
||||
[Inject]
|
||||
private IServiceHubSessionCache ServiceSessionCache { get; init; }
|
||||
|
||||
[Inject]
|
||||
private IUpgradeService UpgradeService { get; init; }
|
||||
|
||||
[Inject]
|
||||
private IDataService DataService { get; set; }
|
||||
|
||||
@ -64,9 +57,15 @@ namespace Remotely.Server.Components.Devices
|
||||
|
||||
[Inject]
|
||||
private IModalService ModalService { get; set; }
|
||||
|
||||
[Inject]
|
||||
private IServiceHubSessionCache ServiceSessionCache { get; init; }
|
||||
|
||||
[Inject]
|
||||
private IToastService ToastService { get; set; }
|
||||
|
||||
[Inject]
|
||||
private IUpgradeService UpgradeService { get; init; }
|
||||
public void Dispose()
|
||||
{
|
||||
AppState.PropertyChanged -= AppState_PropertyChanged;
|
||||
@ -323,5 +322,18 @@ namespace Remotely.Server.Components.Devices
|
||||
ParentFrame.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WakeDevice()
|
||||
{
|
||||
var result = await CircuitConnection.WakeDevice(Device);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
ToastService.ShowToast2("Wake command sent.", ToastType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToastService.ShowToast2($"Wake command failed. Reason: {result.Reason}", ToastType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,9 @@
|
||||
<option @key="group.ID" value="@group.ID">@group.Name</option>
|
||||
}
|
||||
</select>
|
||||
<button class="btn btn-sm btn-secondary" title="Wake Devices" style="width: 40px; margin-left: 5px;" @onclick="WakeDevices">
|
||||
<span class="oi oi-eye"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
@ -257,6 +257,21 @@ namespace Remotely.Server.Components.Devices
|
||||
|
||||
}
|
||||
|
||||
private IEnumerable<Device> GetDevicesInSelectedGroup()
|
||||
{
|
||||
if (_selectedGroupId == _deviceGroupNone)
|
||||
{
|
||||
return _allDevices.Where(x => x.DeviceGroupID is null);
|
||||
}
|
||||
|
||||
if (_selectedGroupId == _deviceGroupAll)
|
||||
{
|
||||
return _allDevices;
|
||||
}
|
||||
|
||||
return _allDevices.Where(x => x.DeviceGroupID == _selectedGroupId);
|
||||
}
|
||||
|
||||
private string GetDisplayName(PropertyInfo propInfo)
|
||||
{
|
||||
return propInfo.GetCustomAttribute<DisplayAttribute>()?.Name ?? propInfo.Name;
|
||||
@ -335,5 +350,22 @@ namespace Remotely.Server.Components.Devices
|
||||
_sortDirection = ListSortDirection.Ascending;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WakeDevices()
|
||||
{
|
||||
var devices = GetDevicesInSelectedGroup();
|
||||
var result = await CircuitConnection.WakeDevices(devices.ToArray());
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
ToastService.ShowToast2("Wake commands sent.", ToastType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToastService.ShowToast2(
|
||||
$"Failed to send wake commands. Reason: {result.Reason}",
|
||||
ToastType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,6 +46,7 @@ namespace Remotely.Server.Data
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
var jsonOptions = JsonSerializerOptions.Default;
|
||||
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
@ -102,8 +103,8 @@ namespace Remotely.Server.Data
|
||||
builder.Entity<RemotelyUser>()
|
||||
.Property(x => x.UserOptions)
|
||||
.HasConversion(
|
||||
x => JsonSerializer.Serialize(x, (JsonSerializerOptions)null),
|
||||
x => JsonSerializer.Deserialize<RemotelyUserOptions>(x, (JsonSerializerOptions)null));
|
||||
x => JsonSerializer.Serialize(x, jsonOptions),
|
||||
x => JsonSerializer.Deserialize<RemotelyUserOptions>(x, jsonOptions));
|
||||
builder.Entity<RemotelyUser>()
|
||||
.HasMany(x => x.SavedScripts)
|
||||
.WithOne(x => x.Creator);
|
||||
@ -117,8 +118,8 @@ namespace Remotely.Server.Data
|
||||
builder.Entity<Device>()
|
||||
.Property(x => x.Drives)
|
||||
.HasConversion(
|
||||
x => JsonSerializer.Serialize(x, (JsonSerializerOptions)null),
|
||||
x => JsonSerializer.Deserialize<List<Drive>>(x, (JsonSerializerOptions)null));
|
||||
x => JsonSerializer.Serialize(x, jsonOptions),
|
||||
x => TryDeserializeProperty<List<Drive>>(x, jsonOptions));
|
||||
builder.Entity<Device>()
|
||||
.Property(x => x.Drives)
|
||||
.Metadata.SetValueComparer(new ValueComparer<List<Drive>>(true));
|
||||
@ -136,6 +137,11 @@ namespace Remotely.Server.Data
|
||||
builder.Entity<Device>()
|
||||
.HasMany(x => x.ScriptSchedules)
|
||||
.WithMany(x => x.Devices);
|
||||
builder.Entity<Device>()
|
||||
.Property(x => x.MacAddresses)
|
||||
.HasConversion(
|
||||
x => JsonSerializer.Serialize(x, jsonOptions),
|
||||
x => DeserializeStringArray(x, jsonOptions));
|
||||
|
||||
builder.Entity<DeviceGroup>()
|
||||
.HasMany(x => x.Devices);
|
||||
@ -153,16 +159,16 @@ namespace Remotely.Server.Data
|
||||
builder.Entity<ScriptResult>()
|
||||
.Property(x => x.ErrorOutput)
|
||||
.HasConversion(
|
||||
x => JsonSerializer.Serialize(x, (JsonSerializerOptions)null),
|
||||
x => JsonSerializer.Deserialize<string[]>(x, (JsonSerializerOptions)null))
|
||||
x => JsonSerializer.Serialize(x, jsonOptions),
|
||||
x => DeserializeStringArray(x, jsonOptions))
|
||||
.Metadata
|
||||
.SetValueComparer(_stringArrayComparer);
|
||||
|
||||
builder.Entity<ScriptResult>()
|
||||
.Property(x => x.StandardOutput)
|
||||
.HasConversion(
|
||||
x => JsonSerializer.Serialize(x, (JsonSerializerOptions)null),
|
||||
x => JsonSerializer.Deserialize<string[]>(x, (JsonSerializerOptions)null))
|
||||
x => JsonSerializer.Serialize(x, jsonOptions),
|
||||
x => DeserializeStringArray(x, jsonOptions))
|
||||
.Metadata
|
||||
.SetValueComparer(_stringArrayComparer);
|
||||
|
||||
@ -198,5 +204,38 @@ namespace Remotely.Server.Data
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static string[] DeserializeStringArray(string value, JsonSerializerOptions jsonOptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
return JsonSerializer.Deserialize<string[]>(value, jsonOptions) ?? Array.Empty<string>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
private static T TryDeserializeProperty<T>(string value, JsonSerializerOptions jsonOptions)
|
||||
where T: new()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return new();
|
||||
}
|
||||
return JsonSerializer.Deserialize<T>(value, jsonOptions) ?? new();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
Server/Enums/ToastType.cs
Normal file
12
Server/Enums/ToastType.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Remotely.Server.Enums
|
||||
{
|
||||
public enum ToastType
|
||||
{
|
||||
Primary,
|
||||
Secondary,
|
||||
Success,
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
}
|
||||
@ -55,6 +55,8 @@ namespace Remotely.Server.Hubs
|
||||
Task UninstallAgents(string[] deviceIDs);
|
||||
Task UpdateTags(string deviceID, string tags);
|
||||
Task UploadFiles(List<string> fileIDs, string transferID, string[] deviceIDs);
|
||||
Task<Result> WakeDevice(Device device);
|
||||
Task<Result> WakeDevices(Device[] deviceId);
|
||||
}
|
||||
|
||||
public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
@ -65,11 +67,11 @@ namespace Remotely.Server.Hubs
|
||||
private readonly IAuthService _authService;
|
||||
private readonly ICircuitManager _circuitManager;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IDesktopHubSessionCache _desktopSessionCache;
|
||||
private readonly ConcurrentQueue<CircuitEvent> _eventQueue = new();
|
||||
private readonly IExpiringTokenService _expiringTokenService;
|
||||
private readonly IDesktopHubSessionCache _desktopSessionCache;
|
||||
private readonly IServiceHubSessionCache _serviceSessionCache;
|
||||
private readonly ILogger<CircuitConnection> _logger;
|
||||
private readonly IServiceHubSessionCache _serviceSessionCache;
|
||||
private readonly IToastService _toastService;
|
||||
public CircuitConnection(
|
||||
IAuthService authService,
|
||||
@ -431,6 +433,89 @@ namespace Remotely.Server.Hubs
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<Result> WakeDevice(Device device)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_dataService.DoesUserHaveAccessToDevice(device.ID, User.Id))
|
||||
{
|
||||
return Result.Fail("Unauthorized.") ;
|
||||
}
|
||||
|
||||
var availableDevices = _serviceSessionCache
|
||||
.GetAllDevices()
|
||||
.Where(x =>
|
||||
x.OrganizationID == User.OrganizationID &&
|
||||
(x.DeviceGroup == device.DeviceGroup || x.PublicIP == device.PublicIP));
|
||||
|
||||
await SendWakeCommand(device, availableDevices);
|
||||
|
||||
return Result.Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error waking device {deviceId}.", device.ID);
|
||||
return Result.Fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result> WakeDevices(Device[] devices)
|
||||
{
|
||||
try
|
||||
{
|
||||
var deviceIds = devices.Select(x => x.ID).ToArray();
|
||||
var filteredIds = _dataService.FilterDeviceIDsByUserPermission(deviceIds, User);
|
||||
var filteredDevices = devices.Where(x => filteredIds.Contains(x.ID)).ToArray();
|
||||
|
||||
var availableDevices = _serviceSessionCache
|
||||
.GetAllDevices()
|
||||
.Where(x => x.OrganizationID == User.OrganizationID);
|
||||
|
||||
var devicesById = new ConcurrentDictionary<string, Device>();
|
||||
var devicesByGroupId = new ConcurrentDictionary<string, List<Device>>();
|
||||
var devicesByPublicIp = new ConcurrentDictionary<string, List<Device>>();
|
||||
|
||||
foreach (var device in availableDevices)
|
||||
{
|
||||
devicesById.AddOrUpdate(device.ID, device, (k, v) => device);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(device.DeviceGroupID))
|
||||
{
|
||||
var group = devicesByGroupId.GetOrAdd(device.DeviceGroupID, key => new());
|
||||
group.Add(device);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(device.PublicIP))
|
||||
{
|
||||
var group = devicesByPublicIp.GetOrAdd(device.PublicIP, key => new());
|
||||
group.Add(device);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var deviceToWake in filteredDevices)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(deviceToWake.DeviceGroupID) &&
|
||||
devicesByGroupId.TryGetValue(deviceToWake.DeviceGroupID, out var groupList))
|
||||
{
|
||||
await SendWakeCommand(deviceToWake, groupList);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(deviceToWake.PublicIP) &&
|
||||
devicesByGroupId.TryGetValue(deviceToWake.PublicIP, out var ipList))
|
||||
{
|
||||
await SendWakeCommand(deviceToWake, ipList);
|
||||
}
|
||||
|
||||
}
|
||||
return Result.Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while waking devices.");
|
||||
return Result.Fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private (bool canAccess, string connectionId) CanAccessDevice(string deviceId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(deviceId))
|
||||
@ -444,7 +529,7 @@ namespace Remotely.Server.Hubs
|
||||
{
|
||||
return (false, string.Empty);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (true, connectionId);
|
||||
}
|
||||
@ -488,5 +573,19 @@ namespace Remotely.Server.Hubs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendWakeCommand(Device deviceToWake, IEnumerable<Device> peerDevices)
|
||||
{
|
||||
foreach (var peerDevice in peerDevices)
|
||||
{
|
||||
foreach (var mac in deviceToWake.MacAddresses)
|
||||
{
|
||||
if (_serviceSessionCache.TryGetConnectionId(peerDevice.ID, out var connectionId))
|
||||
{
|
||||
await _agentHubContext.Clients.Client(connectionId).SendAsync("WakeDevice", mac);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Nihs.ConcurrentList;
|
||||
using Remotely.Server.Enums;
|
||||
using Remotely.Server.Models;
|
||||
using System;
|
||||
using System.Timers;
|
||||
@ -7,11 +8,20 @@ namespace Remotely.Server.Services
|
||||
{
|
||||
public interface IToastService
|
||||
{
|
||||
ConcurrentList<Toast> Toasts { get; }
|
||||
|
||||
event EventHandler OnToastsChanged;
|
||||
|
||||
void ShowToast(string message, int expirationMillisecond = 3000, string classString = null, string styleOverrides = null);
|
||||
ConcurrentList<Toast> Toasts { get; }
|
||||
void ShowToast(
|
||||
string message,
|
||||
int expirationMillisecond = 3000,
|
||||
string classString = null,
|
||||
string styleOverrides = null);
|
||||
|
||||
void ShowToast2(
|
||||
string message,
|
||||
ToastType toastType = ToastType.Info,
|
||||
int expirationMillisecond = 3000,
|
||||
string styleOverrides = null);
|
||||
}
|
||||
|
||||
public class ToastService : IToastService
|
||||
@ -52,5 +62,23 @@ namespace Remotely.Server.Services
|
||||
};
|
||||
removeToastTimer.Start();
|
||||
}
|
||||
|
||||
public void ShowToast2(
|
||||
string message,
|
||||
ToastType toastType,
|
||||
int expirationMillisecond = 3000,
|
||||
string styleOverrides = null)
|
||||
{
|
||||
var classString = toastType switch
|
||||
{
|
||||
ToastType.Info => "bg-info text-white",
|
||||
ToastType.Success => "bg-success text-white",
|
||||
ToastType.Warning => "bg-warning text-white",
|
||||
ToastType.Error => "bg-danger text-white",
|
||||
_ => "bg-info text-white"
|
||||
};
|
||||
|
||||
ShowToast(message, expirationMillisecond, classString, styleOverrides);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,6 +49,10 @@ namespace Remotely.Shared.Models
|
||||
[Display(Name = "Last Online")]
|
||||
public DateTimeOffset LastOnline { get; set; }
|
||||
|
||||
[Sortable]
|
||||
[Display(Name = "MAC Addresses")]
|
||||
public string[] MacAddresses { get; set; } = Array.Empty<string>();
|
||||
|
||||
[StringLength(5000)]
|
||||
public string Notes { get; set; }
|
||||
|
||||
@ -71,19 +75,19 @@ namespace Remotely.Shared.Models
|
||||
public int ProcessorCount { get; set; }
|
||||
|
||||
public string PublicIP { get; set; }
|
||||
public string ServerVerificationToken { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<ScriptResult> ScriptResults { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<ScriptRun> ScriptRuns { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<ScriptRun> ScriptRunsCompleted { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<ScriptSchedule> ScriptSchedules { get; set; }
|
||||
|
||||
public string ServerVerificationToken { get; set; }
|
||||
[StringLength(200)]
|
||||
[Sortable]
|
||||
[Display(Name = "Tags")]
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 2e6914d88aa2d46aec41f1baff10ab7d15d62c5a
|
||||
Subproject commit e1188cd0bee2053cdd8c288601c5539572d9534f
|
||||
Loading…
Reference in New Issue
Block a user