Compare commits
12 Commits
2022.01.31
...
2022.02.02
Author | SHA1 | Date | |
---|---|---|---|
b7a76cc4a2 | |||
261da918c7 | |||
2ed5e00bcb | |||
6ca94f8da8 | |||
3b532cf1f7 | |||
40966ed74d | |||
45eacabc28 | |||
0b02b7627a | |||
fc3a24ca17 | |||
209cb6cdd0 | |||
cfd4296f5c | |||
b275fbaced |
@ -47,6 +47,10 @@ echo making start scripts
|
|||||||
@(echo @echo off && echo @title IW4MAdmin && echo set DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%PublishDir%\StartIW4MAdmin.cmd"
|
@(echo @echo off && echo @title IW4MAdmin && echo set DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%PublishDir%\StartIW4MAdmin.cmd"
|
||||||
@(echo #!/bin/bash&& echo export DOTNET_CLI_TELEMETRY_OPTOUT=1&& echo dotnet Lib/IW4MAdmin.dll) > "%PublishDir%\StartIW4MAdmin.sh"
|
@(echo #!/bin/bash&& echo export DOTNET_CLI_TELEMETRY_OPTOUT=1&& echo dotnet Lib/IW4MAdmin.dll) > "%PublishDir%\StartIW4MAdmin.sh"
|
||||||
|
|
||||||
|
echo copying update scripts
|
||||||
|
copy "%SourceDir%\DeploymentFiles\UpdateIW4MAdmin.ps1" "%PublishDir%\UpdateIW4MAdmin.ps1"
|
||||||
|
copy "%SourceDir%\DeploymentFiles\UpdateIW4MAdmin.sh" "%PublishDir%\UpdateIW4MAdmin.sh"
|
||||||
|
|
||||||
echo moving front-end library dependencies
|
echo moving front-end library dependencies
|
||||||
if not exist "%PublishDir%\wwwroot\font" mkdir "%PublishDir%\wwwroot\font"
|
if not exist "%PublishDir%\wwwroot\font" mkdir "%PublishDir%\wwwroot\font"
|
||||||
move "WebfrontCore\wwwroot\lib\open-iconic\font\fonts\*.*" "%PublishDir%\wwwroot\font\"
|
move "WebfrontCore\wwwroot\lib\open-iconic\font\fonts\*.*" "%PublishDir%\wwwroot\font\"
|
||||||
|
@ -94,7 +94,7 @@ namespace IW4MAdmin.Application.Extensions
|
|||||||
postgresqlOptions =>
|
postgresqlOptions =>
|
||||||
{
|
{
|
||||||
postgresqlOptions.EnableRetryOnFailure();
|
postgresqlOptions.EnableRetryOnFailure();
|
||||||
postgresqlOptions.SetPostgresVersion(new Version("9.4"));
|
postgresqlOptions.SetPostgresVersion(new Version("12.9"));
|
||||||
})
|
})
|
||||||
.UseLoggerFactory(sp.GetRequiredService<ILoggerFactory>()).Options);
|
.UseLoggerFactory(sp.GetRequiredService<ILoggerFactory>()).Options);
|
||||||
return services;
|
return services;
|
||||||
|
@ -12,6 +12,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -25,7 +26,6 @@ using Data.Models;
|
|||||||
using Data.Models.Server;
|
using Data.Models.Server;
|
||||||
using IW4MAdmin.Application.Commands;
|
using IW4MAdmin.Application.Commands;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using SharedLibraryCore.Formatting;
|
|
||||||
using static Data.Models.Client.EFClient;
|
using static Data.Models.Client.EFClient;
|
||||||
|
|
||||||
namespace IW4MAdmin
|
namespace IW4MAdmin
|
||||||
@ -1076,19 +1076,26 @@ namespace IW4MAdmin
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ResolvedIpEndPoint = new IPEndPoint((await Dns.GetHostAddressesAsync(IP)).First(), Port);
|
ResolvedIpEndPoint =
|
||||||
|
new IPEndPoint(
|
||||||
|
(await Dns.GetHostAddressesAsync(IP)).First(address =>
|
||||||
|
address.AddressFamily == AddressFamily.InterNetwork), Port);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ServerLogger.LogWarning(ex, "Could not resolve hostname or IP for RCon connection {IP}:{Port}", IP, Port);
|
ServerLogger.LogWarning(ex, "Could not resolve hostname or IP for RCon connection {IP}:{Port}", IP, Port);
|
||||||
ResolvedIpEndPoint = new IPEndPoint(IPAddress.Parse(IP), Port);
|
ResolvedIpEndPoint = new IPEndPoint(IPAddress.Parse(IP), Port);
|
||||||
}
|
}
|
||||||
|
|
||||||
RconParser = Manager.AdditionalRConParsers
|
RconParser = Manager.AdditionalRConParsers
|
||||||
.FirstOrDefault(_parser => _parser.Version == ServerConfig.RConParserVersion);
|
.FirstOrDefault(parser =>
|
||||||
|
parser.Version == ServerConfig.RConParserVersion ||
|
||||||
|
parser.Name == ServerConfig.RConParserVersion);
|
||||||
|
|
||||||
EventParser = Manager.AdditionalEventParsers
|
EventParser = Manager.AdditionalEventParsers
|
||||||
.FirstOrDefault(_parser => _parser.Version == ServerConfig.EventParserVersion);
|
.FirstOrDefault(parser =>
|
||||||
|
parser.Version == ServerConfig.EventParserVersion ||
|
||||||
|
parser.Name == ServerConfig.RConParserVersion);
|
||||||
|
|
||||||
RconParser ??= Manager.AdditionalRConParsers[0];
|
RconParser ??= Manager.AdditionalRConParsers[0];
|
||||||
EventParser ??= Manager.AdditionalEventParsers[0];
|
EventParser ??= Manager.AdditionalEventParsers[0];
|
||||||
@ -1105,7 +1112,7 @@ namespace IW4MAdmin
|
|||||||
GameName = RconParser.GameName;
|
GameName = RconParser.GameName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version?.Value?.Length != 0)
|
if (version.Value?.Length != 0)
|
||||||
{
|
{
|
||||||
var matchedRconParser = Manager.AdditionalRConParsers.FirstOrDefault(_parser => _parser.Version == version.Value);
|
var matchedRconParser = Manager.AdditionalRConParsers.FirstOrDefault(_parser => _parser.Version == version.Value);
|
||||||
RconParser.Configuration = matchedRconParser != null ? matchedRconParser.Configuration : RconParser.Configuration;
|
RconParser.Configuration = matchedRconParser != null ? matchedRconParser.Configuration : RconParser.Configuration;
|
||||||
|
@ -26,7 +26,8 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
private readonly ApplicationConfiguration _appConfig;
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
private readonly BuildNumber _fallbackVersion = BuildNumber.Parse("99.99.99.99");
|
private readonly BuildNumber _fallbackVersion = BuildNumber.Parse("99.99.99.99");
|
||||||
private readonly int _apiVersion = 1;
|
private readonly int _apiVersion = 1;
|
||||||
private bool firstHeartBeat = true;
|
private bool _firstHeartBeat = true;
|
||||||
|
private static readonly TimeSpan Interval = TimeSpan.FromSeconds(30);
|
||||||
|
|
||||||
public MasterCommunication(ILogger<MasterCommunication> logger, ApplicationConfiguration appConfig, ITranslationLookup translationLookup, IMasterApi apiInstance, IManager manager)
|
public MasterCommunication(ILogger<MasterCommunication> logger, ApplicationConfiguration appConfig, ITranslationLookup translationLookup, IMasterApi apiInstance, IManager manager)
|
||||||
{
|
{
|
||||||
@ -97,7 +98,10 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await UploadStatus();
|
if (_manager.IsRunning)
|
||||||
|
{
|
||||||
|
await UploadStatus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -107,7 +111,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(30000, token);
|
await Task.Delay(Interval, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
catch
|
catch
|
||||||
@ -119,7 +123,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
private async Task UploadStatus()
|
private async Task UploadStatus()
|
||||||
{
|
{
|
||||||
if (firstHeartBeat)
|
if (_firstHeartBeat)
|
||||||
{
|
{
|
||||||
var token = await _apiInstance.Authenticate(new AuthenticationId
|
var token = await _apiInstance.Authenticate(new AuthenticationId
|
||||||
{
|
{
|
||||||
@ -153,7 +157,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
Response<ResultMessage> response = null;
|
Response<ResultMessage> response = null;
|
||||||
|
|
||||||
if (firstHeartBeat)
|
if (_firstHeartBeat)
|
||||||
{
|
{
|
||||||
response = await _apiInstance.AddInstance(instance);
|
response = await _apiInstance.AddInstance(instance);
|
||||||
}
|
}
|
||||||
@ -161,7 +165,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
response = await _apiInstance.UpdateInstance(instance.Id, instance);
|
response = await _apiInstance.UpdateInstance(instance.Id, instance);
|
||||||
firstHeartBeat = false;
|
_firstHeartBeat = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.ResponseMessage.StatusCode != System.Net.HttpStatusCode.OK)
|
if (response.ResponseMessage.StatusCode != System.Net.HttpStatusCode.OK)
|
||||||
|
@ -207,42 +207,30 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
public async Task<IEnumerable<IClientMeta>> GetRuntimeMeta(ClientPaginationRequest request)
|
public async Task<IEnumerable<IClientMeta>> GetRuntimeMeta(ClientPaginationRequest request)
|
||||||
{
|
{
|
||||||
var meta = new List<IClientMeta>();
|
var metas = await Task.WhenAll(_metaActions.Where(kvp => kvp.Key != MetaType.Information)
|
||||||
|
.Select(async kvp => await kvp.Value[0](request)));
|
||||||
|
|
||||||
foreach (var (type, actions) in _metaActions)
|
return metas.SelectMany(m => (IEnumerable<IClientMeta>)m)
|
||||||
{
|
.OrderByDescending(m => m.When)
|
||||||
// information is not listed chronologically
|
|
||||||
if (type != MetaType.Information)
|
|
||||||
{
|
|
||||||
var metaItems = await actions[0](request);
|
|
||||||
meta.AddRange(metaItems);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return meta.OrderByDescending(_meta => _meta.When)
|
|
||||||
.Take(request.Count)
|
.Take(request.Count)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<T>> GetRuntimeMeta<T>(ClientPaginationRequest request, MetaType metaType) where T : IClientMeta
|
public async Task<IEnumerable<T>> GetRuntimeMeta<T>(ClientPaginationRequest request, MetaType metaType) where T : IClientMeta
|
||||||
{
|
{
|
||||||
IEnumerable<T> meta;
|
|
||||||
if (metaType == MetaType.Information)
|
if (metaType == MetaType.Information)
|
||||||
{
|
{
|
||||||
var allMeta = new List<T>();
|
var allMeta = new List<T>();
|
||||||
|
|
||||||
foreach (var individualMetaRegistration in _metaActions[metaType])
|
var completedMeta = await Task.WhenAll(_metaActions[metaType].Select(async individualMetaRegistration =>
|
||||||
{
|
(IEnumerable<T>)await individualMetaRegistration(request)));
|
||||||
allMeta.AddRange(await individualMetaRegistration(request));
|
|
||||||
}
|
allMeta.AddRange(completedMeta.SelectMany(meta => meta));
|
||||||
|
|
||||||
return ProcessInformationMeta(allMeta);
|
return ProcessInformationMeta(allMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
var meta = await _metaActions[metaType][0](request) as IEnumerable<T>;
|
||||||
{
|
|
||||||
meta = await _metaActions[metaType][0](request) as IEnumerable<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return meta;
|
return meta;
|
||||||
}
|
}
|
||||||
|
118
DeploymentFiles/UpdateIW4MAdmin.ps1
Normal file
118
DeploymentFiles/UpdateIW4MAdmin.ps1
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
param (
|
||||||
|
[Parameter(HelpMessage = "Do not prompt for any user input")]
|
||||||
|
[switch]$Silent = $False,
|
||||||
|
|
||||||
|
[Parameter(HelpMessage = "Clean unneeded files listed in _delete.txt after update")]
|
||||||
|
[switch]$Clean = $False,
|
||||||
|
|
||||||
|
[Parameter(HelpMessage = "Only update releases in the verified stream")]
|
||||||
|
[switch]$Verified = $False,
|
||||||
|
|
||||||
|
[Parameter(HelpMessage = "Directory to install to")]
|
||||||
|
[ValidateScript({
|
||||||
|
if (-Not($_ | Test-Path))
|
||||||
|
{
|
||||||
|
throw "File or folder does not exist"
|
||||||
|
} return $true
|
||||||
|
})]
|
||||||
|
[System.IO.FileInfo]$Directory
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Output "======================================="
|
||||||
|
Write-Output " IW4MAdmin Updater v1 "
|
||||||
|
Write-Output " by XERXES & RaidMax "
|
||||||
|
Write-Output "======================================="
|
||||||
|
|
||||||
|
$stopwatch = [system.diagnostics.stopwatch]::StartNew()
|
||||||
|
$repoName = "RaidMax/IW4M-Admin"
|
||||||
|
$assetPattern = "IW4MAdmin-20*.zip"
|
||||||
|
|
||||||
|
if ($Verified)
|
||||||
|
{
|
||||||
|
$releasesUri = "https://api.github.com/repos/$repoName/releases/latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$releasesUri = "https://api.github.com/repos/$repoName/releases"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "Retrieving latest version info..."
|
||||||
|
|
||||||
|
$releaseInfo = (Invoke-WebRequest $releasesUri | ConvertFrom-Json) | Select -First 1
|
||||||
|
$asset = $releaseInfo.assets | Where-Object name -like $assetPattern | Select -First 1
|
||||||
|
$downloadUri = $asset.browser_download_url
|
||||||
|
$filename = Split-Path $downloadUri -leaf
|
||||||
|
|
||||||
|
Write-Output "The latest version is $( $releaseInfo.tag_name ) released $( $releaseInfo.published_at )"
|
||||||
|
|
||||||
|
if (!$Silent)
|
||||||
|
{
|
||||||
|
$stopwatch.Stop()
|
||||||
|
Write-Warning "All IW4MAdmin files will be updated. Your database and configuration will not be modified. Are you sure you want to continue?" -WarningAction Inquire
|
||||||
|
$stopwatch.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "Downloading update. This might take a moment..."
|
||||||
|
|
||||||
|
$fileDownload = Invoke-WebRequest -Uri $downloadUri
|
||||||
|
if ($fileDownload.StatusDescription -ne "OK")
|
||||||
|
{
|
||||||
|
throw "Could not update IW4MAdmin. ($fileDownload.StatusDescription)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$remoteHash = $fileDownload.Headers['Content-MD5']
|
||||||
|
$decodedHash = [System.BitConverter]::ToString([System.Convert]::FromBase64String($remoteHash)).replace('-', '')
|
||||||
|
$directoryPath = Get-Location
|
||||||
|
$fullPath = "$directoryPath\$filename"
|
||||||
|
$outputFile = [System.IO.File]::Open($fullPath, 2)
|
||||||
|
$stream = [System.IO.BinaryWriter]::new($outputFile)
|
||||||
|
|
||||||
|
if ($Directory)
|
||||||
|
{
|
||||||
|
$outputDir = $Directory
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$outputDir = Get-Location
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$stream.Write($fileDownload.Content)
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
$stream.Dispose()
|
||||||
|
$outputFile.Dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
$localHash = (Get-FileHash -Path $fullPath -Algorithm MD5).Hash
|
||||||
|
|
||||||
|
if ($localHash -ne $decodedHash)
|
||||||
|
{
|
||||||
|
throw "Failed to update. File hashes don't match!"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "Extracting $filename to $outputDir"
|
||||||
|
Expand-Archive -Path $fullPath -DestinationPath $outputDir -Force
|
||||||
|
|
||||||
|
if ($Clean)
|
||||||
|
{
|
||||||
|
Write-Output "Running post-update clean..."
|
||||||
|
$DeleteList = Get-Content -Path ./_delete.txt
|
||||||
|
ForEach ($file in $DeleteList)
|
||||||
|
{
|
||||||
|
Write-Output "Deleting $file"
|
||||||
|
Remove-Item -Path $file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "Removing temporary files..."
|
||||||
|
Remove-Item -Force $fullPath
|
||||||
|
|
||||||
|
$stopwatch.Stop()
|
||||||
|
$executionTime = [math]::Round($stopwatch.Elapsed.TotalSeconds, 0)
|
||||||
|
|
||||||
|
Write-Output "Update completed successfully in $executionTime seconds!"
|
106
DeploymentFiles/UpdateIW4MAdmin.sh
Normal file
106
DeploymentFiles/UpdateIW4MAdmin.sh
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
echo "======================================="
|
||||||
|
echo " IW4MAdmin Updater v1 "
|
||||||
|
echo "======================================="
|
||||||
|
|
||||||
|
while getopts scvd: flag
|
||||||
|
do
|
||||||
|
case "${flag}" in
|
||||||
|
s) silent='true';;
|
||||||
|
c) clean='true';;
|
||||||
|
v) verified='true';;
|
||||||
|
d) directory=${OPTARG};;
|
||||||
|
*) exit 1;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
start=$SECONDS
|
||||||
|
repoName="RaidMax/IW4M-Admin"
|
||||||
|
releaseUri="https://api.github.com/repos/$repoName/releases"
|
||||||
|
|
||||||
|
echo "Retrieving latest version info..."
|
||||||
|
|
||||||
|
if [ ! "$directory" ]
|
||||||
|
then
|
||||||
|
directory=$(pwd)
|
||||||
|
else
|
||||||
|
if [ ! -d "$directory" ]
|
||||||
|
then
|
||||||
|
mkdir "$directory"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$verified" ]
|
||||||
|
then
|
||||||
|
releaseUri="https://api.github.com/repos/$repoName/releases/latest"
|
||||||
|
fi
|
||||||
|
|
||||||
|
releaseInfo=$(curl -s "${releaseUri}")
|
||||||
|
downloadUri=$(echo "$releaseInfo" | grep "browser_download_url" | cut -d '"' -f 4"" | head -n1)
|
||||||
|
publishDate=$(echo "$releaseInfo"| grep "published_at" | cut -d '"' -f 4"" | head -n1)
|
||||||
|
releaseTitle=$(echo "$releaseInfo" | grep "tag_name" | cut -d '"' -f 4"" | head -n1)
|
||||||
|
filename=$(basename $downloadUri)
|
||||||
|
fullpath="$directory/$filename"
|
||||||
|
|
||||||
|
echo "The latest version is $releaseTitle released $publishDate"
|
||||||
|
|
||||||
|
if [[ ! "$silent" ]]
|
||||||
|
then
|
||||||
|
echo -e "\033[33mAll IW4MAdmin files will be updated.\033[0m"
|
||||||
|
echo -e "\033[33mYour database and configuration will not be modified.\033[0m"
|
||||||
|
read -p "Are you sure you want to continue [Y/N]? " -n 1 -r
|
||||||
|
echo
|
||||||
|
if ! [[ $REPLY =~ ^[Yy]$ ]]
|
||||||
|
then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Downloading update. This might take a moment..."
|
||||||
|
|
||||||
|
wget -q "$downloadUri" -O "$fullpath"
|
||||||
|
|
||||||
|
if [[ $? -ne 0 ]]
|
||||||
|
then
|
||||||
|
echo "Could not download update files!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Extracting $filename to $directory"
|
||||||
|
|
||||||
|
unzip -o -q "$fullpath" -d "$directory"
|
||||||
|
|
||||||
|
if [[ $? -ne 0 ]]
|
||||||
|
then
|
||||||
|
echo "Could not extract update files!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$clean" ]]
|
||||||
|
then
|
||||||
|
echo "Running post-update clean..."
|
||||||
|
cat "_delete.txt" | while read -r line || [[ -n $line ]];
|
||||||
|
do
|
||||||
|
rm -f "$directory/$line"
|
||||||
|
if [[ $? -ne 0 ]]
|
||||||
|
then
|
||||||
|
echo "Could not clean $directory/$line!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Removing temporary files..."
|
||||||
|
rm -f "$fullpath"
|
||||||
|
|
||||||
|
if [[ $? -ne 0 ]]
|
||||||
|
then
|
||||||
|
echo "Could not remove update files!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod +x "$directory/StartIW4MAdmin.sh"
|
||||||
|
chmod +x "$directory/UpdateIW4MAdmin.sh"
|
||||||
|
|
||||||
|
executionTime=$(($SECONDS - start))
|
||||||
|
echo "Update completed successfully in $executionTime seconds!"
|
@ -121,6 +121,7 @@ steps:
|
|||||||
script: |
|
script: |
|
||||||
echo changing to encoding for linux start script
|
echo changing to encoding for linux start script
|
||||||
dos2unix $(outputFolder)\StartIW4MAdmin.sh
|
dos2unix $(outputFolder)\StartIW4MAdmin.sh
|
||||||
|
dos2unix $(outputFolder)\UpdateIW4MAdmin.sh
|
||||||
echo creating website version filename
|
echo creating website version filename
|
||||||
@echo IW4MAdmin-$(Build.BuildNumber) > $(Build.ArtifactStagingDirectory)\version_$(releaseType).txt
|
@echo IW4MAdmin-$(Build.BuildNumber) > $(Build.ArtifactStagingDirectory)\version_$(releaseType).txt
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
||||||
|
@ -12,6 +12,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
DeploymentFiles\PostPublish.ps1 = DeploymentFiles\PostPublish.ps1
|
DeploymentFiles\PostPublish.ps1 = DeploymentFiles\PostPublish.ps1
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
version.txt = version.txt
|
version.txt = version.txt
|
||||||
|
DeploymentFiles\UpdateIW4MAdmin.ps1 = DeploymentFiles\UpdateIW4MAdmin.ps1
|
||||||
|
DeploymentFiles\UpdateIW4MAdmin.sh = DeploymentFiles\UpdateIW4MAdmin.sh
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedLibraryCore", "SharedLibraryCore\SharedLibraryCore.csproj", "{AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedLibraryCore", "SharedLibraryCore\SharedLibraryCore.csproj", "{AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}"
|
||||||
|
@ -191,7 +191,6 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
async Task<IEnumerable<InformationResponse>> getStats(ClientPaginationRequest request)
|
async Task<IEnumerable<InformationResponse>> getStats(ClientPaginationRequest request)
|
||||||
{
|
{
|
||||||
IList<EFClientStatistics> clientStats;
|
IList<EFClientStatistics> clientStats;
|
||||||
int messageCount = 0;
|
|
||||||
await using var ctx = _databaseContextFactory.CreateContext(enableTracking: false);
|
await using var ctx = _databaseContextFactory.CreateContext(enableTracking: false);
|
||||||
clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == request.ClientId).ToListAsync();
|
clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == request.ClientId).ToListAsync();
|
||||||
|
|
||||||
|
11
README.md
11
README.md
@ -35,11 +35,22 @@ Linux
|
|||||||
* You will need to retrieve your login credentials by typing `!rt` ingame
|
* You will need to retrieve your login credentials by typing `!rt` ingame
|
||||||
|
|
||||||
### Updating
|
### Updating
|
||||||
|
**Manually**
|
||||||
1. Download the latest version of **IW4MAdmin**
|
1. Download the latest version of **IW4MAdmin**
|
||||||
2. Extract the newer version of **IW4MAdmin** into pre-existing **IW4MAdmin** folder and overwrite existing files
|
2. Extract the newer version of **IW4MAdmin** into pre-existing **IW4MAdmin** folder and overwrite existing files
|
||||||
|
|
||||||
_Your configuration and database will be saved_
|
_Your configuration and database will be saved_
|
||||||
|
|
||||||
|
**OR**
|
||||||
|
Use the provided `UpdateIW4MAdmin` script to download and install automatically
|
||||||
|
|
||||||
|
| Argument Windows (Linux) | Description |
|
||||||
|
|--------------------------|-----------------------------------------------------------|
|
||||||
|
| -Silent (s) | Do not prompt for any user input |
|
||||||
|
| -Clean (c) | Clean unneeded files listed in `_delete.txt` after update |
|
||||||
|
| -Verified (v) | Only update releases in the verified stream |
|
||||||
|
| -Directory (d) | Directory to install to |
|
||||||
|
|
||||||
## Help
|
## Help
|
||||||
Feel free to join the **IW4MAdmin** [Discord](https://discord.gg/ZZFK5p3)
|
Feel free to join the **IW4MAdmin** [Discord](https://discord.gg/ZZFK5p3)
|
||||||
If you come across an issue, bug, or feature request please post an [issue](https://github.com/RaidMax/IW4M-Admin/issues)
|
If you come across an issue, bug, or feature request please post an [issue](https://github.com/RaidMax/IW4M-Admin/issues)
|
||||||
|
@ -147,8 +147,8 @@ namespace SharedLibraryCore.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
_selectedParser = _rconParsers.FirstOrDefault(p => p.Name == parser);
|
_selectedParser = _rconParsers.FirstOrDefault(p => p.Name == parser);
|
||||||
RConParserVersion = _selectedParser?.Version;
|
RConParserVersion = _selectedParser?.Name;
|
||||||
EventParserVersion = _selectedParser?.Version;
|
EventParserVersion = _selectedParser?.Name;
|
||||||
|
|
||||||
if (index <= 0 || _rconParsers[index].CanGenerateLogPath)
|
if (index <= 0 || _rconParsers[index].CanGenerateLogPath)
|
||||||
{
|
{
|
||||||
|
@ -168,10 +168,9 @@ namespace SharedLibraryCore.Services
|
|||||||
|
|
||||||
public async Task<EFClient> Get(int entityId)
|
public async Task<EFClient> Get(int entityId)
|
||||||
{
|
{
|
||||||
// todo: this needs to be optimized for large linked accounts
|
|
||||||
await using var context = _contextFactory.CreateContext(false);
|
await using var context = _contextFactory.CreateContext(false);
|
||||||
|
|
||||||
var client = context.Clients
|
var client = await context.Clients
|
||||||
.Select(_client => new EFClient
|
.Select(_client => new EFClient
|
||||||
{
|
{
|
||||||
ClientId = _client.ClientId,
|
ClientId = _client.ClientId,
|
||||||
@ -187,26 +186,28 @@ namespace SharedLibraryCore.Services
|
|||||||
Name = _client.CurrentAlias.Name,
|
Name = _client.CurrentAlias.Name,
|
||||||
IPAddress = _client.CurrentAlias.IPAddress
|
IPAddress = _client.CurrentAlias.IPAddress
|
||||||
},
|
},
|
||||||
TotalConnectionTime = _client.TotalConnectionTime
|
TotalConnectionTime = _client.TotalConnectionTime,
|
||||||
|
AliasLink = new EFAliasLink
|
||||||
|
{
|
||||||
|
AliasLinkId = _client.AliasLinkId,
|
||||||
|
Children = _client.AliasLink.Children
|
||||||
|
},
|
||||||
|
LinkedAccounts = new Dictionary<int, long>()
|
||||||
|
{
|
||||||
|
{_client.ClientId, _client.NetworkId}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.FirstOrDefault(_client => _client.ClientId == entityId);
|
.FirstOrDefaultAsync(_client => _client.ClientId == entityId);
|
||||||
|
|
||||||
if (client == null)
|
if (client == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.AliasLink = new EFAliasLink
|
if (!_appConfig.EnableImplicitAccountLinking)
|
||||||
{
|
{
|
||||||
AliasLinkId = client.AliasLinkId,
|
return client;
|
||||||
Children = await context.Aliases
|
}
|
||||||
.Where(_alias => _alias.LinkId == client.AliasLinkId)
|
|
||||||
.Select(_alias => new EFAlias
|
|
||||||
{
|
|
||||||
Name = _alias.Name,
|
|
||||||
IPAddress = _alias.IPAddress
|
|
||||||
}).ToListAsync()
|
|
||||||
};
|
|
||||||
|
|
||||||
var foundClient = new
|
var foundClient = new
|
||||||
{
|
{
|
||||||
@ -220,11 +221,6 @@ namespace SharedLibraryCore.Services
|
|||||||
.ToListAsync()
|
.ToListAsync()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (foundClient == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
foundClient.Client.LinkedAccounts = new Dictionary<int, long>();
|
foundClient.Client.LinkedAccounts = new Dictionary<int, long>();
|
||||||
// todo: find out the best way to do this
|
// todo: find out the best way to do this
|
||||||
// I'm doing this here because I don't know the best way to have multiple awaits in the query
|
// I'm doing this here because I don't know the best way to have multiple awaits in the query
|
||||||
|
@ -163,9 +163,6 @@ namespace SharedLibraryCore.Services
|
|||||||
(p.Expires == null || p.Expires > now);
|
(p.Expires == null || p.Expires > now);
|
||||||
|
|
||||||
await using var context = _contextFactory.CreateContext(false);
|
await using var context = _contextFactory.CreateContext(false);
|
||||||
var iqLinkPenalties = context.Penalties
|
|
||||||
.Where(p => p.LinkId == linkId)
|
|
||||||
.Where(filter);
|
|
||||||
|
|
||||||
IQueryable<EFPenalty> iqIpPenalties;
|
IQueryable<EFPenalty> iqIpPenalties;
|
||||||
|
|
||||||
@ -178,35 +175,6 @@ namespace SharedLibraryCore.Services
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* var aliasIps = await context.Aliases.Where(alias => (alias.LinkId == linkId || alias.AliasId == currentAliasId) && alias.IPAddress != null)
|
|
||||||
.Select(alias => alias.IPAddress)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
if (ip != null)
|
|
||||||
{
|
|
||||||
aliasIps.Add(ip);
|
|
||||||
}
|
|
||||||
|
|
||||||
var clientIds = new List<int>();
|
|
||||||
|
|
||||||
if (aliasIps.Any())
|
|
||||||
{
|
|
||||||
clientIds = await context.Clients.Where(client => aliasIps.Contains(client.CurrentAlias.IPAddress))
|
|
||||||
.Select(client => client.ClientId).ToListAsync();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clientIds.Any())
|
|
||||||
{
|
|
||||||
iqIpPenalties = context.Penalties.Where(penalty => clientIds.Contains(penalty.OffenderId))
|
|
||||||
.Where(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
iqIpPenalties = Enumerable.Empty<EFPenalty>().AsQueryable();
|
|
||||||
}*/
|
|
||||||
|
|
||||||
var usedIps = await context.Aliases.AsNoTracking()
|
var usedIps = await context.Aliases.AsNoTracking()
|
||||||
.Where(alias => (alias.LinkId == linkId || alias.AliasId == currentAliasId) && alias.IPAddress != null)
|
.Where(alias => (alias.LinkId == linkId || alias.AliasId == currentAliasId) && alias.IPAddress != null)
|
||||||
.Select(alias => alias.IPAddress).ToListAsync();
|
.Select(alias => alias.IPAddress).ToListAsync();
|
||||||
@ -216,13 +184,12 @@ namespace SharedLibraryCore.Services
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
iqIpPenalties = context.Penalties.AsNoTracking()
|
iqIpPenalties = context.Penalties.AsNoTracking()
|
||||||
.Where(penalty => aliasedIds.Contains(penalty.LinkId))
|
.Where(penalty => aliasedIds.Contains(penalty.LinkId) || penalty.LinkId == linkId)
|
||||||
.Where(filter);
|
.Where(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
var activeLinkPenalties = await iqLinkPenalties.ToListAsync();
|
|
||||||
var activeIpPenalties = await iqIpPenalties.ToListAsync();
|
var activeIpPenalties = await iqIpPenalties.ToListAsync();
|
||||||
var activePenalties = activeLinkPenalties.Concat(activeIpPenalties).Distinct();
|
var activePenalties = activeIpPenalties.Distinct();
|
||||||
|
|
||||||
// this is a bit more performant in memory (ordering)
|
// this is a bit more performant in memory (ordering)
|
||||||
return activePenalties.OrderByDescending(p => p.When).ToList();
|
return activePenalties.OrderByDescending(p => p.When).ToList();
|
||||||
|
@ -10,7 +10,6 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Models;
|
using Data.Models;
|
||||||
using IW4MAdmin.Plugins.Stats.Config;
|
|
||||||
using Stats.Config;
|
using Stats.Config;
|
||||||
using WebfrontCore.ViewComponents;
|
using WebfrontCore.ViewComponents;
|
||||||
|
|
||||||
@ -38,7 +37,16 @@ namespace WebfrontCore.Controllers
|
|||||||
|
|
||||||
var activePenalties = await Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId, client.CurrentAliasId, client.IPAddress);
|
var activePenalties = await Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId, client.CurrentAliasId, client.IPAddress);
|
||||||
|
|
||||||
var tag = await _metaService.GetPersistentMeta(EFMeta.ClientTag, client);
|
var persistentMetaTask = new[]
|
||||||
|
{
|
||||||
|
_metaService.GetPersistentMeta(EFMeta.ClientTag, client),
|
||||||
|
_metaService.GetPersistentMeta("GravatarEmail", client)
|
||||||
|
};
|
||||||
|
|
||||||
|
var persistentMeta = await Task.WhenAll(persistentMetaTask);
|
||||||
|
var tag = persistentMeta[0];
|
||||||
|
var gravatar = persistentMeta[1];
|
||||||
|
|
||||||
if (tag?.LinkedMeta != null)
|
if (tag?.LinkedMeta != null)
|
||||||
{
|
{
|
||||||
client.SetAdditionalProperty(EFMeta.ClientTag, tag.LinkedMeta.Value);
|
client.SetAdditionalProperty(EFMeta.ClientTag, tag.LinkedMeta.Value);
|
||||||
@ -55,7 +63,7 @@ namespace WebfrontCore.Controllers
|
|||||||
|
|
||||||
displayLevel = string.IsNullOrEmpty(client.Tag) ? displayLevel : $"{displayLevel} ({client.Tag})";
|
displayLevel = string.IsNullOrEmpty(client.Tag) ? displayLevel : $"{displayLevel} ({client.Tag})";
|
||||||
|
|
||||||
var clientDto = new PlayerInfo()
|
var clientDto = new PlayerInfo
|
||||||
{
|
{
|
||||||
Name = client.Name,
|
Name = client.Name,
|
||||||
Level = displayLevel,
|
Level = displayLevel,
|
||||||
@ -92,7 +100,6 @@ namespace WebfrontCore.Controllers
|
|||||||
Before = DateTime.UtcNow
|
Before = DateTime.UtcNow
|
||||||
}, MetaType.Information);
|
}, MetaType.Information);
|
||||||
|
|
||||||
var gravatar = await _metaService.GetPersistentMeta("GravatarEmail", client);
|
|
||||||
if (gravatar != null)
|
if (gravatar != null)
|
||||||
{
|
{
|
||||||
clientDto.Meta.Add(new InformationResponse()
|
clientDto.Meta.Add(new InformationResponse()
|
||||||
@ -106,7 +113,7 @@ namespace WebfrontCore.Controllers
|
|||||||
clientDto.ActivePenalty = activePenalties.OrderByDescending(_penalty => _penalty.Type).FirstOrDefault();
|
clientDto.ActivePenalty = activePenalties.OrderByDescending(_penalty => _penalty.Type).FirstOrDefault();
|
||||||
clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.IsSensitive));
|
clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.IsSensitive));
|
||||||
|
|
||||||
string strippedName = clientDto.Name.StripColors();
|
var strippedName = clientDto.Name.StripColors();
|
||||||
ViewBag.Title = strippedName.Substring(strippedName.Length - 1).ToLower()[0] == 's' ?
|
ViewBag.Title = strippedName.Substring(strippedName.Length - 1).ToLower()[0] == 's' ?
|
||||||
strippedName + "'" :
|
strippedName + "'" :
|
||||||
strippedName + "'s";
|
strippedName + "'s";
|
||||||
|
@ -96,7 +96,7 @@
|
|||||||
$('.ip-locate-link').click(function (e) {
|
$('.ip-locate-link').click(function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const ip = $(this).data("ip");
|
const ip = $(this).data("ip");
|
||||||
$.getJSON( `http://ip-api.com/json/${ip}`)
|
$.getJSON(`https://ipwhois.app/json/${ip}`)
|
||||||
.done(function (response) {
|
.done(function (response) {
|
||||||
$('#mainModal .modal-title').text(ip);
|
$('#mainModal .modal-title').text(ip);
|
||||||
$('#mainModal .modal-body').text('');
|
$('#mainModal .modal-body').text('');
|
||||||
@ -106,18 +106,21 @@
|
|||||||
if (response.org.length > 0) {
|
if (response.org.length > 0) {
|
||||||
$('#mainModal .modal-body').append(`${_localization['WEBFRONT_PROFILE_LOOKUP_ORG']} — ${response.org}<br/>`);
|
$('#mainModal .modal-body').append(`${_localization['WEBFRONT_PROFILE_LOOKUP_ORG']} — ${response.org}<br/>`);
|
||||||
}
|
}
|
||||||
if (response.regionName.length > 0 || response.city.length > 0 || response.country.length > 0) {
|
if (response.region.length > 0 || response.city.length > 0 || response.country.length > 0 || response.timezone_gmt.length > 0) {
|
||||||
$('#mainModal .modal-body').append(`${_localization['WEBFRONT_PROFILE_LOOKUP_LOCATION']} — `);
|
$('#mainModal .modal-body').append(`${_localization['WEBFRONT_PROFILE_LOOKUP_LOCATION']} — `);
|
||||||
}
|
}
|
||||||
if (response.city.length > 0) {
|
if (response.city.length > 0) {
|
||||||
$('#mainModal .modal-body').append(response.city);
|
$('#mainModal .modal-body').append(response.city);
|
||||||
}
|
}
|
||||||
if (response.regionName.length > 0) {
|
if (response.region.length > 0) {
|
||||||
$('#mainModal .modal-body').append((response.regionName.length > 0 ? ', ' : '') + response.region);
|
$('#mainModal .modal-body').append((response.region.length > 0 ? ', ' : '') + response.region);
|
||||||
}
|
}
|
||||||
if (response.country.length > 0) {
|
if (response.country.length > 0) {
|
||||||
$('#mainModal .modal-body').append((response.country.length > 0 ? ', ' : '') + response.country);
|
$('#mainModal .modal-body').append((response.country.length > 0 ? ', ' : '') + response.country);
|
||||||
}
|
}
|
||||||
|
if (response.timezone_gmt.length > 0) {
|
||||||
|
$('#mainModal .modal-body').append((response.timezone_gmt.length > 0 ? ', ' : '') + response.timezone_gmt);
|
||||||
|
}
|
||||||
|
|
||||||
$('#mainModal').modal();
|
$('#mainModal').modal();
|
||||||
})
|
})
|
||||||
|
@ -11,6 +11,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(() => {
|
$(document).ready(() => {
|
||||||
|
if ($('.scoreboard-container').length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
$(window.location.hash).tab('show');
|
$(window.location.hash).tab('show');
|
||||||
$(`${window.location.hash}_nav`).addClass('active');
|
$(`${window.location.hash}_nav`).addClass('active');
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user