mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
337 lines
11 KiB
Plaintext
337 lines
11 KiB
Plaintext
@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
|
|
|
|
@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>
|
|
</div>
|
|
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" readonly value="@_windowsScript" />
|
|
<button class="btn btn-primary" type="button" id="button-addon2" @onclick="() => CopyScriptToClipboard(_windowsScript)">
|
|
<i class="oi oi-clipboard"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-sm-12 mb-3">
|
|
<div class="mb-1">
|
|
<strong>Ubuntu (64-Bit)</strong>
|
|
</div>
|
|
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" readonly value="@_ubuntuScript" />
|
|
<button class="btn btn-primary" type="button" id="button-addon2" @onclick="() => CopyScriptToClipboard(_ubuntuScript)">
|
|
<i class="oi oi-clipboard"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-sm-12 mb-3">
|
|
<div class="mb-1">
|
|
<strong>Manjaro (64-Bit)</strong>
|
|
</div>
|
|
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" readonly value="@_manjaroScript" />
|
|
<button class="btn btn-primary" type="button" id="button-addon2" @onclick="() => CopyScriptToClipboard(_manjaroScript)">
|
|
<i class="oi oi-clipboard"></i>
|
|
</button>
|
|
</div>
|
|
</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-info mb-2">
|
|
Created Date: @(_winX64CreatedDate) UTC
|
|
</div>
|
|
}
|
|
|
|
@if (_isServerAdmin)
|
|
{
|
|
<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 class="mt-1">
|
|
<span class="text-primary">
|
|
Note: Only server admins will have this button.
|
|
</span>
|
|
</div>
|
|
</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="() => CopyUriToClipboard(_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-info mb-2">
|
|
Created Date: @(_winX86CreatedDate) UTC
|
|
</div>
|
|
}
|
|
|
|
@if (_isServerAdmin)
|
|
{
|
|
<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 class="mt-1">
|
|
<span class="text-primary">
|
|
Note: Only server admins will have this button.
|
|
</span>
|
|
</div>
|
|
</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="() => CopyUriToClipboard(_winX86Uri)">
|
|
<i class="oi oi-clipboard"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@code {
|
|
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 bool _isServerAdmin;
|
|
private DateTime _winX64CreatedDate;
|
|
private DateTime _winX86CreatedDate;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
var userResult = await Auth.GetUser();
|
|
if (!userResult.IsSuccess)
|
|
{
|
|
NavMan.NavigateTo("/", false);
|
|
return;
|
|
}
|
|
|
|
_isServerAdmin = userResult.Value.IsServerAdmin;
|
|
_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();
|
|
|
|
await base.OnInitializedAsync();
|
|
}
|
|
|
|
private async Task CopyScriptToClipboard(string script)
|
|
{
|
|
try
|
|
{
|
|
var result = await JsInterop.SetClipboardText(script);
|
|
if (result)
|
|
{
|
|
Toasts.ShowToast2("Script copied to clipboard", ToastType.Success);
|
|
return;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Error while copying script to clipboard.");
|
|
}
|
|
Toasts.ShowToast2("Failed to set clipboard content", ToastType.Error);
|
|
}
|
|
|
|
private async Task CopyUriToClipboard(string uri)
|
|
{
|
|
try
|
|
{
|
|
var result = await JsInterop.SetClipboardText(uri);
|
|
if (result)
|
|
{
|
|
Toasts.ShowToast2("URI copied to clipboard", ToastType.Success);
|
|
return;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Error while copying URI to clipboard.");
|
|
}
|
|
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 =
|
|
$"Invoke-WebRequest -Uri '{NavMan.BaseUri}api/ClientDownloads/WindowsInstaller/{_organizationId}' -OutFile \"${{env:TEMP}}\\Install-Remotely.ps1\" -UseBasicParsing;" +
|
|
"Start-Process -FilePath 'powershell.exe' -ArgumentList (\"-executionpolicy\", \"bypass\", \"-f\", \"${env:TEMP}\\Install-Remotely.ps1\") -Verb RunAs;";
|
|
|
|
_ubuntuScript = GetLinuxScript("UbuntuInstaller-x64");
|
|
|
|
_manjaroScript = GetLinuxScript("ManjaroInstaller-x64");
|
|
}
|
|
|
|
private async Task TrySaveFile(IFileInfo? fileInfo, InputFileChangeEventArgs ev)
|
|
{
|
|
// Since this is server-side Blazor, it would be impossible to
|
|
// get to this point without being a server admin. But we'll add
|
|
// it here to prevent any issues caused by future changes.
|
|
if (!_isServerAdmin)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
} |