mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
Add ability to upload custom versions of the attended support client.
This commit is contained in:
parent
f46567fec8
commit
cd3057dac1
@ -5,71 +5,71 @@ pr:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
#- job: Mac_Build
|
||||
# displayName: Mac Build
|
||||
# timeoutInMinutes: 360
|
||||
# pool:
|
||||
# vmImage: macos-latest
|
||||
# steps:
|
||||
#
|
||||
# - task: InstallSSHKey@0
|
||||
# inputs:
|
||||
# knownHostsEntry: |
|
||||
# github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
|
||||
# github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
|
||||
# github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
|
||||
# sshKeySecureFile: 'pipelines_rsa'
|
||||
#
|
||||
# - checkout: self
|
||||
# submodules: recursive
|
||||
# clean: true
|
||||
# fetchTags: false
|
||||
#
|
||||
# - task: PowerShell@2
|
||||
# displayName: Add CurrentVersion Variable
|
||||
# inputs:
|
||||
# targetType: inline
|
||||
# script: |
|
||||
# $VersionString = git show -s --format=%ci $(Build.SourceVersion)
|
||||
# $VersionDate = [DateTimeOffset]::Parse($VersionString)
|
||||
# $CurrentVersion = $VersionDate.ToString("yyyy.MM.dd.HHmm")
|
||||
#
|
||||
# [System.Console]::WriteLine("##vso[task.setvariable variable=CurrentVersion]$CurrentVersion")
|
||||
#
|
||||
# Write-Host "Setting current version to $CurrentVersion."
|
||||
#
|
||||
# - task: UseDotNet@2
|
||||
# displayName: Use .NET SDK
|
||||
# inputs:
|
||||
# version: 8.x
|
||||
#
|
||||
# - task: DotNetCoreCLI@2
|
||||
# displayName: dotnet publish x64
|
||||
# inputs:
|
||||
# command: publish
|
||||
# publishWebProjects: false
|
||||
# projects: '**/Agent.csproj'
|
||||
# arguments: -c $(BuildConfiguration) -r osx-x64 -o "$(Build.SourcesDirectory)/Agent/bin/publish" /p:Version=$(CurrentVersion) /p:FileVersion=$(CurrentVersion)
|
||||
# zipAfterPublish: false
|
||||
# modifyOutputPath: false
|
||||
#
|
||||
# - task: PowerShell@2
|
||||
# displayName: PowerShell Script
|
||||
# inputs:
|
||||
# targetType: inline
|
||||
# script: |
|
||||
# Compress-Archive -Path "$(Build.SourcesDirectory)/Agent/bin/publish/*" -DestinationPath "$(Build.SourcesDirectory)/Agent/bin/Remotely-MacOS-x64.zip" -Force
|
||||
#
|
||||
# - task: PublishPipelineArtifact@1
|
||||
# displayName: Publish macOS x64 Agent
|
||||
# inputs:
|
||||
# path: $(Build.SourcesDirectory)/Agent/bin/Remotely-MacOS-x64.zip
|
||||
# artifactName: Mac-x64-Agent
|
||||
- job: Mac_Build
|
||||
displayName: Mac Build
|
||||
timeoutInMinutes: 360
|
||||
pool:
|
||||
vmImage: macos-latest
|
||||
steps:
|
||||
|
||||
- task: InstallSSHKey@0
|
||||
inputs:
|
||||
knownHostsEntry: |
|
||||
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
|
||||
github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
|
||||
github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
|
||||
sshKeySecureFile: 'pipelines_rsa'
|
||||
|
||||
- checkout: self
|
||||
submodules: recursive
|
||||
clean: true
|
||||
fetchTags: false
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: Add CurrentVersion Variable
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
$VersionString = git show -s --format=%ci $(Build.SourceVersion)
|
||||
$VersionDate = [DateTimeOffset]::Parse($VersionString)
|
||||
$CurrentVersion = $VersionDate.ToString("yyyy.MM.dd.HHmm")
|
||||
|
||||
[System.Console]::WriteLine("##vso[task.setvariable variable=CurrentVersion]$CurrentVersion")
|
||||
|
||||
Write-Host "Setting current version to $CurrentVersion."
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK
|
||||
inputs:
|
||||
version: 8.x
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: dotnet publish x64
|
||||
inputs:
|
||||
command: publish
|
||||
publishWebProjects: false
|
||||
projects: '**/Agent.csproj'
|
||||
arguments: -c $(BuildConfiguration) -r osx-x64 -o "$(Build.SourcesDirectory)/Agent/bin/publish" /p:Version=$(CurrentVersion) /p:FileVersion=$(CurrentVersion)
|
||||
zipAfterPublish: false
|
||||
modifyOutputPath: false
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: PowerShell Script
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
Compress-Archive -Path "$(Build.SourcesDirectory)/Agent/bin/publish/*" -DestinationPath "$(Build.SourcesDirectory)/Agent/bin/Remotely-MacOS-x64.zip" -Force
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish macOS x64 Agent
|
||||
inputs:
|
||||
path: $(Build.SourcesDirectory)/Agent/bin/Remotely-MacOS-x64.zip
|
||||
artifactName: Mac-x64-Agent
|
||||
|
||||
- job: Windows_Build
|
||||
displayName: Windows Build
|
||||
timeoutInMinutes: 360
|
||||
#dependsOn: Mac_Build
|
||||
dependsOn: Mac_Build
|
||||
pool:
|
||||
vmImage: windows-latest
|
||||
|
||||
@ -90,11 +90,11 @@ jobs:
|
||||
- task: VisualStudioTestPlatformInstaller@1
|
||||
displayName: Visual Studio Test Platform Installer
|
||||
|
||||
# - task: DownloadPipelineArtifact@2
|
||||
# displayName: Download macOS x64 Agent
|
||||
# inputs:
|
||||
# artifact: Mac-x64-Agent
|
||||
# path: $(Build.SourcesDirectory)\Server\wwwroot\Content\
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download macOS x64 Agent
|
||||
inputs:
|
||||
artifact: Mac-x64-Agent
|
||||
path: $(Build.SourcesDirectory)\Server\wwwroot\Content\
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: Add CurrentVersion Variable
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -296,3 +296,4 @@ Server.Installer/Properties/launchSettings.json
|
||||
!/.vscode/launch.json
|
||||
!/.vscode/tasks.json
|
||||
/Server/appsettings.Development.json
|
||||
/Server/AppData
|
||||
|
||||
@ -33,7 +33,7 @@ public class Program
|
||||
var logger = new FileLogger("Remotely_Desktop", version, "Program.cs");
|
||||
var filePath = Environment.ProcessPath ?? Environment.GetCommandLineArgs().First();
|
||||
var serverUrl = Debugger.IsAttached ? "http://localhost:5000" : string.Empty;
|
||||
var getEmbeddedResult = EmbeddedServerDataSearcher.Instance.TryGetEmbeddedData(filePath);
|
||||
var getEmbeddedResult = EmbeddedServerDataProvider.Instance.TryGetEmbeddedData(filePath);
|
||||
if (getEmbeddedResult.IsSuccess)
|
||||
{
|
||||
serverUrl = getEmbeddedResult.Value.ServerUrl.AbsoluteUri;
|
||||
@ -46,7 +46,7 @@ public class Program
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.AddSingleton<IOrganizationIdProvider, OrganizationIdProvider>();
|
||||
services.AddSingleton<IEmbeddedServerDataSearcher, EmbeddedServerDataSearcher>();
|
||||
services.AddSingleton<IEmbeddedServerDataProvider, EmbeddedServerDataProvider>();
|
||||
|
||||
services.AddRemoteControlLinux(
|
||||
config =>
|
||||
|
||||
@ -13,7 +13,7 @@ namespace Desktop.Shared.Services;
|
||||
public class BrandingProvider : IBrandingProvider
|
||||
{
|
||||
private readonly IAppState _appState;
|
||||
private readonly IEmbeddedServerDataSearcher _embeddedDataSearcher;
|
||||
private readonly IEmbeddedServerDataProvider _embeddedDataSearcher;
|
||||
private readonly ILogger<BrandingProvider> _logger;
|
||||
private readonly IOrganizationIdProvider _orgIdProvider;
|
||||
private BrandingInfoBase? _brandingInfo;
|
||||
@ -22,7 +22,7 @@ public class BrandingProvider : IBrandingProvider
|
||||
public BrandingProvider(
|
||||
IAppState appState,
|
||||
IOrganizationIdProvider orgIdProvider,
|
||||
IEmbeddedServerDataSearcher embeddedServerDataSearcher,
|
||||
IEmbeddedServerDataProvider embeddedServerDataSearcher,
|
||||
ILogger<BrandingProvider> logger)
|
||||
{
|
||||
_appState = appState;
|
||||
|
||||
@ -35,7 +35,7 @@ public class Program
|
||||
var logger = new FileLogger("Remotely_Desktop", version, "Program.cs");
|
||||
var filePath = Environment.ProcessPath ?? Environment.GetCommandLineArgs().First();
|
||||
var serverUrl = Debugger.IsAttached ? "https://localhost:5001" : string.Empty;
|
||||
var getEmbeddedResult = EmbeddedServerDataSearcher.Instance.TryGetEmbeddedData(filePath);
|
||||
var getEmbeddedResult = EmbeddedServerDataProvider.Instance.TryGetEmbeddedData(filePath);
|
||||
if (getEmbeddedResult.IsSuccess)
|
||||
{
|
||||
serverUrl = getEmbeddedResult.Value.ServerUrl.AbsoluteUri;
|
||||
@ -47,7 +47,7 @@ public class Program
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.AddSingleton<IOrganizationIdProvider, OrganizationIdProvider>();
|
||||
services.AddSingleton<IEmbeddedServerDataSearcher>(EmbeddedServerDataSearcher.Instance);
|
||||
services.AddSingleton<IEmbeddedServerDataProvider>(EmbeddedServerDataProvider.Instance);
|
||||
|
||||
services.AddRemoteControlWindows(
|
||||
config =>
|
||||
|
||||
@ -15,14 +15,14 @@ namespace Remotely.Server.API;
|
||||
public class ClientDownloadsController : ControllerBase
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IEmbeddedServerDataSearcher _embeddedDataSearcher;
|
||||
private readonly IEmbeddedServerDataProvider _embeddedDataSearcher;
|
||||
private readonly SemaphoreSlim _fileLock = new(1, 1);
|
||||
private readonly IWebHostEnvironment _hostEnv;
|
||||
private readonly ILogger<ClientDownloadsController> _logger;
|
||||
|
||||
public ClientDownloadsController(
|
||||
IWebHostEnvironment hostEnv,
|
||||
IEmbeddedServerDataSearcher embeddedDataSearcher,
|
||||
IEmbeddedServerDataProvider embeddedDataSearcher,
|
||||
IDataService dataService,
|
||||
ILogger<ClientDownloadsController> logger)
|
||||
{
|
||||
@ -39,27 +39,27 @@ public class ClientDownloadsController : ControllerBase
|
||||
{
|
||||
case "WindowsDesktop-x64":
|
||||
{
|
||||
var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Win-x64", "Remotely_Desktop.exe");
|
||||
var filePath = Path.Combine("Content", "Win-x64", "Remotely_Desktop.exe");
|
||||
return await GetDesktopFile(filePath);
|
||||
}
|
||||
case "WindowsDesktop-x86":
|
||||
{
|
||||
var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Win-x86", "Remotely_Desktop.exe");
|
||||
var filePath = Path.Combine("Content", "Win-x86", "Remotely_Desktop.exe");
|
||||
return await GetDesktopFile(filePath);
|
||||
}
|
||||
case "UbuntuDesktop":
|
||||
{
|
||||
var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Linux-x64", "Remotely_Desktop");
|
||||
var filePath = Path.Combine("Content", "Linux-x64", "Remotely_Desktop");
|
||||
return await GetDesktopFile(filePath);
|
||||
}
|
||||
case "MacOS-x64":
|
||||
{
|
||||
var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "MacOS-x64", "Remotely_Desktop");
|
||||
var filePath = Path.Combine("Content", "MacOS-x64", "Remotely_Desktop");
|
||||
return await GetDesktopFile(filePath);
|
||||
}
|
||||
case "MacOS-arm64":
|
||||
{
|
||||
var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "MacOS-arm64", "Remotely_Desktop");
|
||||
var filePath = Path.Combine("Content", "MacOS-arm64", "Remotely_Desktop");
|
||||
return await GetDesktopFile(filePath);
|
||||
}
|
||||
default:
|
||||
@ -75,27 +75,27 @@ public class ClientDownloadsController : ControllerBase
|
||||
{
|
||||
case "WindowsDesktop-x64":
|
||||
{
|
||||
var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Win-x64", "Remotely_Desktop.exe");
|
||||
var filePath = Path.Combine("Content", "Win-x64", "Remotely_Desktop.exe");
|
||||
return await GetDesktopFile(filePath, organizationId);
|
||||
}
|
||||
case "WindowsDesktop-x86":
|
||||
{
|
||||
var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Win-x86", "Remotely_Desktop.exe");
|
||||
var filePath = Path.Combine("Content", "Win-x86", "Remotely_Desktop.exe");
|
||||
return await GetDesktopFile(filePath, organizationId);
|
||||
}
|
||||
case "UbuntuDesktop":
|
||||
{
|
||||
var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Linux-x64", "Remotely_Desktop");
|
||||
var filePath = Path.Combine("Content", "Linux-x64", "Remotely_Desktop");
|
||||
return await GetDesktopFile(filePath, organizationId);
|
||||
}
|
||||
case "MacOS-x64":
|
||||
{
|
||||
var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "MacOS-x64", "Remotely_Desktop");
|
||||
var filePath = Path.Combine("Content", "MacOS-x64", "Remotely_Desktop");
|
||||
return await GetDesktopFile(filePath);
|
||||
}
|
||||
case "MacOS-arm64":
|
||||
{
|
||||
var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "MacOS-arm64", "Remotely_Desktop");
|
||||
var filePath = Path.Combine("Content", "MacOS-arm64", "Remotely_Desktop");
|
||||
return await GetDesktopFile(filePath);
|
||||
}
|
||||
default:
|
||||
@ -137,14 +137,9 @@ public class ClientDownloadsController : ControllerBase
|
||||
return File(fileBytes, "application/octet-stream", fileName);
|
||||
}
|
||||
|
||||
private async Task<IActionResult> GetDesktopFile(string filePath, string? organizationId = null)
|
||||
private async Task<IActionResult> GetDesktopFile(string relativeFilePath, string? organizationId = null)
|
||||
{
|
||||
var settings = await _dataService.GetSettings();
|
||||
await LogRequest(nameof(GetDesktopFile));
|
||||
|
||||
var effectiveScheme = settings.ForceClientHttps ? "https" : Request.Scheme;
|
||||
var serverUrl = $"{effectiveScheme}://{Request.Host}";
|
||||
|
||||
var defaultOrg = await _dataService.GetDefaultOrganization();
|
||||
|
||||
// The default org will be used if unspecified, so might as well save the
|
||||
@ -154,10 +149,14 @@ public class ClientDownloadsController : ControllerBase
|
||||
{
|
||||
organizationId = null;
|
||||
}
|
||||
|
||||
var settings = await _dataService.GetSettings();
|
||||
var effectiveScheme = settings.ForceClientHttps ? "https" : Request.Scheme;
|
||||
var serverUrl = $"{effectiveScheme}://{Request.Host}";
|
||||
|
||||
var embeddedData = new EmbeddedServerData(new Uri(serverUrl), organizationId);
|
||||
var fileName = _embeddedDataSearcher.GetEncodedFileName(filePath, embeddedData);
|
||||
var fileInfo = _hostEnv.WebRootFileProvider.GetFileInfo(filePath.Replace(_hostEnv.WebRootPath, string.Empty));
|
||||
return File(fileInfo.CreateReadStream(), "application/octet-stream", fileName);
|
||||
var fileName = _embeddedDataSearcher.GetEncodedFileName(relativeFilePath, embeddedData);
|
||||
return File(relativeFilePath, "application/octet-stream", fileName);
|
||||
}
|
||||
|
||||
private async Task<IActionResult> GetInstallFile(string organizationId, string platformID)
|
||||
|
||||
52
Server/API/CustomBinariesController.cs
Normal file
52
Server/API/CustomBinariesController.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared.Models;
|
||||
using Remotely.Shared.Services;
|
||||
|
||||
namespace Remotely.Server.API;
|
||||
|
||||
[Route("api/custom-binaries")]
|
||||
[ApiController]
|
||||
public class CustomBinariesController(
|
||||
IDataService _dataService,
|
||||
IWebHostEnvironment _hostingEnvironment,
|
||||
IEmbeddedServerDataProvider _embeddedData) : ControllerBase
|
||||
{
|
||||
[HttpGet("win-x86/desktop/{organizationId}")]
|
||||
public async Task<IActionResult> GetWinX86Desktop(string organizationId)
|
||||
{
|
||||
var embeddedData = await GetEmbeddedData(organizationId);
|
||||
var filePath = Path.Combine(_hostingEnvironment.ContentRootPath, "AppData", "Win-x86", "Remotely_Desktop.exe");
|
||||
var fileName = _embeddedData.GetEncodedFileName(filePath, embeddedData);
|
||||
var rs = System.IO.File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
return File(rs, "application/octet-stream", fileName);
|
||||
}
|
||||
|
||||
[HttpGet("win-x64/desktop/{organizationId}")]
|
||||
public async Task<IActionResult> GetWinX64Desktop(string organizationId)
|
||||
{
|
||||
var embeddedData = await GetEmbeddedData(organizationId);
|
||||
var filePath = Path.Combine(_hostingEnvironment.ContentRootPath, "AppData", "Win-x64", "Remotely_Desktop.exe");
|
||||
var fileName = _embeddedData.GetEncodedFileName(filePath, embeddedData);
|
||||
var rs = System.IO.File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
return File(rs, "application/octet-stream", fileName);
|
||||
}
|
||||
|
||||
private async Task<EmbeddedServerData> GetEmbeddedData(string? organizationId)
|
||||
{
|
||||
var defaultOrg = await _dataService.GetDefaultOrganization();
|
||||
|
||||
// The default org will be used if unspecified, so might as well save the
|
||||
// space in the file name.
|
||||
if (defaultOrg.IsSuccess &&
|
||||
defaultOrg.Value.ID.Equals(organizationId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
organizationId = null;
|
||||
}
|
||||
|
||||
var settings = await _dataService.GetSettings();
|
||||
var effectiveScheme = settings.ForceClientHttps ? "https" : Request.Scheme;
|
||||
var serverUrl = $"{effectiveScheme}://{Request.Host}";
|
||||
return new EmbeddedServerData(new Uri(serverUrl), organizationId);
|
||||
}
|
||||
}
|
||||
@ -59,7 +59,9 @@
|
||||
{
|
||||
foreach (var kvp in _fileUploadProgressLookup)
|
||||
{
|
||||
<AlertBanner Message="@GetProgressMessage(kvp.Key)" />
|
||||
<div class="alert alert-info">
|
||||
@(GetProgressMessage(kvp.Key))
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<div>
|
||||
|
||||
@ -7,6 +7,7 @@ using Remotely.Server.Hubs;
|
||||
using Remotely.Server.Models.Messages;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Server.Services.Stores;
|
||||
using Remotely.Shared;
|
||||
using Remotely.Shared.Entities;
|
||||
using Remotely.Shared.Enums;
|
||||
using Remotely.Shared.Utilities;
|
||||
@ -36,19 +37,22 @@ public partial class DeviceCard : AuthComponentBase
|
||||
|
||||
|
||||
[Inject]
|
||||
private ISelectedCardsStore SelectedCards { get; init; } = null!;
|
||||
public required ISelectedCardsStore SelectedCards { get; init; }
|
||||
|
||||
[Inject]
|
||||
private IThemeProvider ThemeProvider { get; init; } = null!;
|
||||
public required IThemeProvider ThemeProvider { get; init; }
|
||||
|
||||
[Inject]
|
||||
private ICircuitConnection CircuitConnection { get; init; } = null!;
|
||||
public required ICircuitConnection CircuitConnection { get; init; }
|
||||
|
||||
[Inject]
|
||||
private IDataService DataService { get; init; } = null!;
|
||||
public required IDataService DataService { get; init; }
|
||||
|
||||
[Inject]
|
||||
private IChatSessionStore ChatCache { get; init; } = null!;
|
||||
public required IChatSessionStore ChatCache { get; init; }
|
||||
|
||||
[Inject]
|
||||
public required ILogger<DeviceCard> Logger { get; init; }
|
||||
|
||||
private bool IsExpanded => _state == DeviceCardState.Expanded;
|
||||
|
||||
@ -220,23 +224,44 @@ public partial class DeviceCard : AuthComponentBase
|
||||
|
||||
private async Task OnFileInputChanged(InputFileChangeEventArgs args)
|
||||
{
|
||||
EnsureUserSet();
|
||||
|
||||
ToastService.ShowToast("File upload started.");
|
||||
|
||||
var fileId = await DataService.AddSharedFile(args.File, User.OrganizationID, OnFileInputProgress);
|
||||
|
||||
var transferId = Guid.NewGuid().ToString();
|
||||
|
||||
var result = await CircuitConnection.TransferFileFromBrowserToAgent(Device.ID, transferId, new[] { fileId });
|
||||
|
||||
if (!result)
|
||||
try
|
||||
{
|
||||
ToastService.ShowToast("Device not found.", classString: "bg-warning");
|
||||
EnsureUserSet();
|
||||
|
||||
ToastService.ShowToast("File upload started.");
|
||||
|
||||
if (args.File.Size > AppConstants.MaxUploadFileSize)
|
||||
{
|
||||
var maxFileSize = AppConstants.MaxUploadFileSize / 1000 / 1000;
|
||||
ToastService.ShowToast2($"File size exceeds the maximum allowed size of {maxFileSize}MB.", ToastType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var fileId = await DataService.AddSharedFile(args.File, User.OrganizationID, OnFileInputProgress);
|
||||
var transferId = Guid.NewGuid().ToString();
|
||||
var result = await CircuitConnection.TransferFileFromBrowserToAgent(Device.ID, transferId, [fileId]);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
ToastService.ShowToast("Device not found.", classString: "bg-warning");
|
||||
}
|
||||
else
|
||||
{
|
||||
ToastService.ShowToast("File upload completed.");
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
ToastService.ShowToast("File upload completed.");
|
||||
Logger.LogError(ex, "Error while uploading file to device.");
|
||||
ToastService.ShowToast2("Failed to upload file", ToastType.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (args.File.Name is not null)
|
||||
{
|
||||
_ = _fileUploadProgressLookup.TryRemove(args.File.Name, out _);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,6 +277,7 @@ public partial class DeviceCard : AuthComponentBase
|
||||
_fileUploadProgressLookup.AddOrUpdate(fileName, percentComplete, (k, v) => percentComplete);
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private void OpenDeviceDetails()
|
||||
{
|
||||
JsInterop.OpenWindow($"/device-details/{Device.ID}", "_blank");
|
||||
|
||||
@ -1,18 +1,29 @@
|
||||
@page "/deploy"
|
||||
@using Microsoft.Extensions.FileProviders
|
||||
@using System.Diagnostics
|
||||
@attribute [Authorize]
|
||||
@inject NavigationManager NavMan
|
||||
@inject IAuthService Auth
|
||||
@inject IDataService DataService
|
||||
@inject IJsInterop JsInterop
|
||||
@inject IToastService Toasts
|
||||
@inject IWebHostEnvironment HostEnv
|
||||
@inject ILogger<Deploy> Logger
|
||||
|
||||
<h3>Deploy Scripts</h3>
|
||||
<p class="text-info col-sm-12 mb-4">
|
||||
@if (_isLoading)
|
||||
{
|
||||
<LoadingSignal StatusMessage="@_loadingMessage" />
|
||||
}
|
||||
|
||||
<h4 class="text-success mt-3">
|
||||
Persistent Agents
|
||||
</h4>
|
||||
<p class="text-info mb-3">
|
||||
Copy and paste on a remote computer to install the agent.
|
||||
</p>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-12 mb-3">
|
||||
<div class="mb-1">
|
||||
<strong>Windows 10/11 (32-Bit and 64-Bit)</strong>
|
||||
@ -53,33 +64,125 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="text-success mt-3">
|
||||
Attended Support
|
||||
</h4>
|
||||
<p class="text-info">
|
||||
You can upload custom versions of the attended support client (i.e. "Remotely_Desktop.exe")
|
||||
and send the download link to end-users when they need support. For example, you could sign
|
||||
the EXE with a commercial certificate so the end-user (hopefully) doesn't see a SmartScreen
|
||||
warning about the file.
|
||||
</p>
|
||||
<p class="mb-3 text-warning">
|
||||
NOTE: These binaries must be manually updated each time a new Docker image is pulled.
|
||||
</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12 mb-4">
|
||||
<div class="mb-1">
|
||||
<strong>Windows 10/11 (64-Bit)</strong>
|
||||
</div>
|
||||
|
||||
@if (_winX64File?.Exists != true)
|
||||
{
|
||||
<div class="text-danger mb-2">
|
||||
Note: A custom binary for this file hasn't been uploaded yet.
|
||||
</div>
|
||||
}
|
||||
else if (_winX64File?.PhysicalPath is not null)
|
||||
{
|
||||
<div class="text-primary mb-2">
|
||||
Created Date: @(_winX64CreatedDate) UTC
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mb-2">
|
||||
<FileInputButton ClassNames="btn btn-primary"
|
||||
OnChanged="HandleWin64FileChanged">
|
||||
<ButtonContent>
|
||||
<i class="oi oi-data-transfer-upload" title="Upload File"></i>
|
||||
<span class="ms-2">Upload File</span>
|
||||
</ButtonContent>
|
||||
</FileInputButton>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" readonly value="@_winX64Uri" />
|
||||
<button class="btn btn-primary" type="button" id="button-addon2" @onclick="() => CopyToClipboard(_winX64Uri)">
|
||||
<i class="oi oi-clipboard"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 mb-4">
|
||||
<div class="mb-1">
|
||||
<strong>Windows 10/11 (32-Bit)</strong>
|
||||
</div>
|
||||
|
||||
@if(_winX86File?.Exists != true)
|
||||
{
|
||||
<div class="text-danger mb-2">
|
||||
Note: A custom binary for this file hasn't been uploaded yet.
|
||||
</div>
|
||||
}
|
||||
else if (_winX86File?.PhysicalPath is not null)
|
||||
{
|
||||
<div class="text-primary mb-2">
|
||||
Created Date: @(_winX86CreatedDate) UTC
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mb-2">
|
||||
<FileInputButton ClassNames="btn btn-primary"
|
||||
OnChanged="HandleWin86FileChanged">
|
||||
<ButtonContent>
|
||||
<i class="oi oi-data-transfer-upload" title="Upload File"></i>
|
||||
<span class="ms-2">Upload File</span>
|
||||
</ButtonContent>
|
||||
</FileInputButton>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" readonly value="@_winX86Uri" />
|
||||
<button class="btn btn-primary" type="button" id="button-addon2" @onclick="() => CopyToClipboard(_winX86Uri)">
|
||||
<i class="oi oi-clipboard"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string? _organizationId;
|
||||
private bool _isAuthenticated;
|
||||
private static readonly SemaphoreSlim _writeLock = new(1, 1);
|
||||
private string _organizationId = string.Empty;
|
||||
private string _windowsScript = string.Empty;
|
||||
private string _ubuntuScript = string.Empty;
|
||||
private string _manjaroScript = string.Empty;
|
||||
private string _appDataDir = string.Empty;
|
||||
private IFileInfo? _winX64File;
|
||||
private IFileInfo? _winX86File;
|
||||
private string _winX64Uri = string.Empty;
|
||||
private string _winX86Uri = string.Empty;
|
||||
private bool _isLoading = false;
|
||||
private string _loadingMessage = string.Empty;
|
||||
private DateTime _winX64CreatedDate;
|
||||
private DateTime _winX86CreatedDate;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_isAuthenticated = await Auth.IsAuthenticated();
|
||||
var userResult = await Auth.GetUser();
|
||||
if (!userResult.IsSuccess)
|
||||
{
|
||||
NavMan.NavigateTo("/", false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isAuthenticated)
|
||||
{
|
||||
var userResult = await Auth.GetUser();
|
||||
if (userResult.IsSuccess)
|
||||
{
|
||||
_organizationId = userResult.Value.OrganizationID;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var orgResult = await DataService.GetDefaultOrganization();
|
||||
if (orgResult.IsSuccess)
|
||||
{
|
||||
_organizationId = orgResult.Value.ID;
|
||||
}
|
||||
}
|
||||
_organizationId = userResult.Value.OrganizationID;
|
||||
_appDataDir = Path.Combine(HostEnv.ContentRootPath, "AppData");
|
||||
_winX64Uri = $"{NavMan.BaseUri}api/custom-binaries/win-x64/desktop/{_organizationId}";
|
||||
_winX86Uri = $"{NavMan.BaseUri}api/custom-binaries/win-x86/desktop/{_organizationId}";
|
||||
|
||||
SetFileInfos();
|
||||
|
||||
SetScriptContent();
|
||||
|
||||
@ -104,6 +207,44 @@
|
||||
Toasts.ShowToast2("Failed to set clipboard content", ToastType.Error);
|
||||
}
|
||||
|
||||
|
||||
private string GetLinuxScript(string platformId)
|
||||
{
|
||||
return
|
||||
$"sudo rm -f /tmp/Install-Remotely.sh && " +
|
||||
$"sudo wget -q -O /tmp/Install-Remotely.sh {NavMan.BaseUri}api/ClientDownloads/{platformId}/{_organizationId} && " +
|
||||
$"sudo chmod +x /tmp/Install-Remotely.sh && " +
|
||||
$"sudo /tmp/Install-Remotely.sh";
|
||||
}
|
||||
|
||||
|
||||
private async Task HandleWin64FileChanged(InputFileChangeEventArgs ev)
|
||||
{
|
||||
await TrySaveFile(_winX64File, ev);
|
||||
}
|
||||
|
||||
private async Task HandleWin86FileChanged(InputFileChangeEventArgs ev)
|
||||
{
|
||||
await TrySaveFile(_winX86File, ev);
|
||||
}
|
||||
|
||||
private void SetFileInfos()
|
||||
{
|
||||
_winX64File = HostEnv.ContentRootFileProvider.GetFileInfo("AppData/Win-x64/Remotely_Desktop.exe");
|
||||
if (_winX64File?.Exists == true && _winX64File?.PhysicalPath is not null)
|
||||
{
|
||||
var fileInfo = new FileInfo(_winX64File.PhysicalPath);
|
||||
_winX64CreatedDate = fileInfo.CreationTimeUtc;
|
||||
}
|
||||
|
||||
_winX86File = HostEnv.ContentRootFileProvider.GetFileInfo("AppData/Win-x86/Remotely_Desktop.exe");
|
||||
if (_winX86File?.Exists == true && _winX86File?.PhysicalPath is not null)
|
||||
{
|
||||
var fileInfo = new FileInfo(_winX86File.PhysicalPath);
|
||||
_winX86CreatedDate = fileInfo.CreationTimeUtc;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetScriptContent()
|
||||
{
|
||||
_windowsScript =
|
||||
@ -115,12 +256,39 @@
|
||||
_manjaroScript = GetLinuxScript("ManjaroInstaller-x64");
|
||||
}
|
||||
|
||||
private string GetLinuxScript(string platformId)
|
||||
private async Task TrySaveFile(IFileInfo? fileInfo, InputFileChangeEventArgs ev)
|
||||
{
|
||||
return
|
||||
$"sudo rm -f /tmp/Install-Remotely.sh && " +
|
||||
$"sudo wget -q -O /tmp/Install-Remotely.sh {NavMan.BaseUri}api/ClientDownloads/{platformId}/{_organizationId} && " +
|
||||
$"sudo chmod +x /tmp/Install-Remotely.sh && " +
|
||||
$"sudo /tmp/Install-Remotely.sh";
|
||||
await _writeLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (fileInfo?.PhysicalPath is null)
|
||||
{
|
||||
Toasts.ShowToast2("Unable to find save path", ToastType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
_loadingMessage = "Uploading file";
|
||||
_isLoading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
_ = Directory.CreateDirectory(Path.GetDirectoryName(fileInfo.PhysicalPath)!);
|
||||
await using var rs = ev.File.OpenReadStream(500_000_000);
|
||||
await using var fs = File.Open(fileInfo.PhysicalPath, FileMode.Create);
|
||||
await rs.CopyToAsync(fs);
|
||||
Toasts.ShowToast2("Custom binary uploaded successfully", ToastType.Success);
|
||||
SetFileInfos();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error while uploading custom binary.");
|
||||
Toasts.ShowToast2("Failed to upload custom binary", ToastType.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_writeLock.Release();
|
||||
_isLoading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -25,4 +25,5 @@
|
||||
@using Remotely.Server.Components.TreeView
|
||||
@using Remotely.Server.Auth
|
||||
@using Remotely.Shared.Entities
|
||||
@using Remotely.Server.Models
|
||||
@using Remotely.Server.Models
|
||||
@using Remotely.Shared.Services;
|
||||
@ -193,7 +193,6 @@ services.Configure<ForwardedHeadersOptions>(options =>
|
||||
services.AddSignalR(options =>
|
||||
{
|
||||
options.EnableDetailedErrors = builder.Environment.IsDevelopment();
|
||||
options.MaximumParallelInvocationsPerClient = 5;
|
||||
options.MaximumReceiveMessageSize = 100_000;
|
||||
})
|
||||
.AddJsonProtocol(options =>
|
||||
@ -246,7 +245,7 @@ services.AddScoped<ISelectedCardsStore, SelectedCardsStore>();
|
||||
services.AddScoped<IExpiringTokenService, ExpiringTokenService>();
|
||||
services.AddScoped<IScriptScheduleDispatcher, ScriptScheduleDispatcher>();
|
||||
services.AddSingleton<IOtpProvider, OtpProvider>();
|
||||
services.AddSingleton<IEmbeddedServerDataSearcher, EmbeddedServerDataSearcher>();
|
||||
services.AddSingleton<IEmbeddedServerDataProvider, EmbeddedServerDataProvider>();
|
||||
services.AddSingleton<ILogsManager, LogsManager>();
|
||||
services.AddScoped<IThemeProvider, ThemeProvider>();
|
||||
services.AddScoped<IChatSessionStore, ChatSessionStore>();
|
||||
|
||||
@ -15,6 +15,7 @@ using Remotely.Shared;
|
||||
using Remotely.Shared.Dtos;
|
||||
using Remotely.Shared.Entities;
|
||||
using Remotely.Shared.Enums;
|
||||
using Remotely.Shared.Extensions;
|
||||
using Remotely.Shared.Models;
|
||||
using Remotely.Shared.Utilities;
|
||||
using Remotely.Shared.ViewModels;
|
||||
@ -552,18 +553,26 @@ public class DataService : IDataService
|
||||
|
||||
public async Task<string> AddSharedFile(IBrowserFile file, string organizationId, Action<double, string> progressCallback)
|
||||
{
|
||||
var fileContents = new byte[file.Size];
|
||||
using var stream = file.OpenReadStream(AppConstants.MaxUploadFileSize);
|
||||
var fileSize = file.Size;
|
||||
var fileName = file.Name;
|
||||
|
||||
for (var i = 0; i < file.Size; i += 5_000)
|
||||
var fileContents = new byte[fileSize];
|
||||
var stream = file.OpenReadStream(AppConstants.MaxUploadFileSize);
|
||||
|
||||
var bytesRead = 0;
|
||||
while (bytesRead < fileSize)
|
||||
{
|
||||
var readSize = (int)Math.Min(5_000, file.Size - i);
|
||||
await stream.ReadAsync(fileContents.AsMemory(i, readSize));
|
||||
|
||||
progressCallback.Invoke((double)stream.Position / stream.Length, file.Name);
|
||||
var segmentEnd = Math.Min(50_000, fileSize - bytesRead);
|
||||
var read = await stream.ReadAsync(fileContents.AsMemory(bytesRead, (int)segmentEnd));
|
||||
if (read == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
bytesRead += read;
|
||||
progressCallback.Invoke((double)bytesRead / fileSize, fileName);
|
||||
}
|
||||
|
||||
progressCallback.Invoke(1, file.Name);
|
||||
progressCallback.Invoke(1, fileName);
|
||||
|
||||
return await AddSharedFileImpl(file.Name, fileContents, file.ContentType, organizationId);
|
||||
}
|
||||
|
||||
@ -2,30 +2,21 @@
|
||||
|
||||
using Immense.RemoteControl.Shared;
|
||||
using MessagePack;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Shared.Entities;
|
||||
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.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Shared.Services;
|
||||
|
||||
public interface IEmbeddedServerDataSearcher
|
||||
public interface IEmbeddedServerDataProvider
|
||||
{
|
||||
string GetEncodedFileName(string filePath, EmbeddedServerData serverData);
|
||||
Result<EmbeddedServerData> TryGetEmbeddedData(string filePath);
|
||||
}
|
||||
|
||||
public class EmbeddedServerDataSearcher() : IEmbeddedServerDataSearcher
|
||||
public class EmbeddedServerDataProvider : IEmbeddedServerDataProvider
|
||||
{
|
||||
public static EmbeddedServerDataSearcher Instance { get; } = new();
|
||||
public static EmbeddedServerDataProvider Instance { get; } = new();
|
||||
|
||||
public string GetEncodedFileName(string filePath, EmbeddedServerData serverData)
|
||||
{
|
||||
@ -1 +1 @@
|
||||
Subproject commit 6c77d32dd6d307ad60d9acd6bbe2c98f2b9b50a9
|
||||
Subproject commit 9151b7534e56a7fa7f2233fa5a415a14afc9005c
|
||||
Loading…
Reference in New Issue
Block a user