adding master api project
This commit is contained in:
parent
9aea9e1c02
commit
23ec72e6b6
4
.gitignore
vendored
4
.gitignore
vendored
@ -221,4 +221,6 @@ DEPLOY
|
||||
global.min.css
|
||||
global.min.js
|
||||
bootstrap-custom.css
|
||||
bootstrap-custom.min.css
|
||||
bootstrap-custom.min.css
|
||||
**/Master/static
|
||||
**/Master/dev_env
|
20
Application/API/Master/ApiInstance.cs
Normal file
20
Application/API/Master/ApiInstance.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using RestEase;
|
||||
|
||||
namespace IW4MAdmin.Application.API.Master
|
||||
{
|
||||
public class ApiInstance
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
[JsonProperty("uptime")]
|
||||
public int Uptime { get; set; }
|
||||
[JsonProperty("version")]
|
||||
public float Version { get; set; }
|
||||
[JsonProperty("servers")]
|
||||
public List<ApiServer> Servers { get; set; }
|
||||
}
|
||||
}
|
27
Application/API/Master/ApiServer.cs
Normal file
27
Application/API/Master/ApiServer.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace IW4MAdmin.Application.API.Master
|
||||
{
|
||||
public class ApiServer
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
[JsonProperty("port")]
|
||||
public short Port { get; set; }
|
||||
[JsonProperty("gametype")]
|
||||
public string Gametype { get; set; }
|
||||
[JsonProperty("map")]
|
||||
public string Map { get; set; }
|
||||
[JsonProperty("game")]
|
||||
public string Game { get; set; }
|
||||
[JsonProperty("hostname")]
|
||||
public string Hostname { get; set; }
|
||||
[JsonProperty("clientnum")]
|
||||
public int ClientNum { get; set; }
|
||||
[JsonProperty("maxclientnum")]
|
||||
public int MaxClientNum { get; set; }
|
||||
}
|
||||
}
|
60
Application/API/Master/Heartbeat.cs
Normal file
60
Application/API/Master/Heartbeat.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using RestEase;
|
||||
using SharedLibraryCore;
|
||||
|
||||
namespace IW4MAdmin.Application.API.Master
|
||||
{
|
||||
public class Heartbeat
|
||||
{
|
||||
static IMasterApi api;
|
||||
|
||||
public static async Task Send(ApplicationManager mgr, bool firstHeartbeat = false)
|
||||
{
|
||||
|
||||
if (firstHeartbeat)
|
||||
{
|
||||
api = RestClient.For<IMasterApi>("http://127.0.0.1");
|
||||
|
||||
var token = await api.Authenticate(new AuthenticationId()
|
||||
{
|
||||
Id = mgr.GetApplicationSettings().Configuration().Id
|
||||
});
|
||||
|
||||
api.AuthorizationToken = $"Bearer {token.AccessToken}";
|
||||
}
|
||||
|
||||
var instance = new ApiInstance()
|
||||
{
|
||||
Id = mgr.GetApplicationSettings().Configuration().Id,
|
||||
Uptime = (int)(DateTime.UtcNow - mgr.StartTime).TotalSeconds,
|
||||
Version = (float)Program.Version,
|
||||
Servers = mgr.Servers.Select(s =>
|
||||
new ApiServer()
|
||||
{
|
||||
ClientNum = s.ClientNum,
|
||||
Game = s.GameName.ToString(),
|
||||
Gametype = s.Gametype,
|
||||
Hostname = s.Hostname,
|
||||
Map = s.CurrentMap.Name,
|
||||
MaxClientNum = s.MaxClients,
|
||||
Id = s.GetHashCode(),
|
||||
Port = (short)s.GetPort()
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
if (firstHeartbeat)
|
||||
{
|
||||
instance = await api.AddInstance(instance);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
instance = await api.UpdateInstance(instance.Id, instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
Application/API/Master/IMasterApi.cs
Normal file
37
Application/API/Master/IMasterApi.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using RestEase;
|
||||
|
||||
namespace IW4MAdmin.Application.API.Master
|
||||
{
|
||||
public class AuthenticationId
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
public class TokenId
|
||||
{
|
||||
[JsonProperty("access_token")]
|
||||
public string AccessToken { get; set; }
|
||||
}
|
||||
|
||||
[Header("User-Agent", "IW4MAdmin-RestEase")]
|
||||
public interface IMasterApi
|
||||
{
|
||||
[Header("Authorization")]
|
||||
string AuthorizationToken { get; set; }
|
||||
|
||||
[Post("authenticate")]
|
||||
Task<TokenId> Authenticate([Body] AuthenticationId Id);
|
||||
|
||||
[Post("instance/")]
|
||||
Task<ApiInstance> AddInstance([Body] ApiInstance instance);
|
||||
|
||||
[Put("instance/{id}")]
|
||||
Task<ApiInstance> UpdateInstance([Path] string id, [Body] ApiInstance instance);
|
||||
}
|
||||
}
|
@ -30,6 +30,10 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RestEase" Version="1.4.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SharedLibraryCore\SharedLibraryCore.csproj">
|
||||
<Private>true</Private>
|
||||
|
@ -51,7 +51,7 @@ namespace Application.EventParsers
|
||||
};
|
||||
}
|
||||
|
||||
if (lineSplit[0].Contains("ShutdownGame"))
|
||||
if (lineSplit[0].Contains("ExitLevel"))
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
@ -69,6 +69,11 @@ namespace Application.EventParsers
|
||||
};
|
||||
}
|
||||
|
||||
/*if (lineSplit[0].Contains("ShutdownGame"))
|
||||
{
|
||||
|
||||
}*/
|
||||
|
||||
if (lineSplit[0].Contains("InitGame"))
|
||||
{
|
||||
return new GameEvent()
|
||||
|
@ -65,6 +65,7 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
Task.Run(() => WebfrontCore.Program.Init(ServerManager));
|
||||
}
|
||||
|
||||
ServerManager.Start();
|
||||
ServerManager.Logger.WriteVerbose("Shutdown complete");
|
||||
|
||||
|
@ -29,6 +29,7 @@ namespace IW4MAdmin.Application
|
||||
public ILogger Logger { get; private set; }
|
||||
public bool Running { get; private set; }
|
||||
public EventHandler<GameEvent> ServerEventOccurred { get; private set; }
|
||||
public DateTime StartTime { get; private set; }
|
||||
|
||||
static ApplicationManager Instance;
|
||||
List<AsyncStatus> TaskStatuses;
|
||||
@ -60,6 +61,7 @@ namespace IW4MAdmin.Application
|
||||
ServerEventOccurred += Api.OnServerEvent;
|
||||
ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
|
||||
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
|
||||
StartTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private void OnCancelKey(object sender, ConsoleCancelEventArgs args)
|
||||
@ -133,6 +135,15 @@ namespace IW4MAdmin.Application
|
||||
await ConfigHandler.Save();
|
||||
}
|
||||
|
||||
else if(config != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(config.Id))
|
||||
{
|
||||
config.Id = Guid.NewGuid().ToString();
|
||||
await ConfigHandler.Save();
|
||||
}
|
||||
}
|
||||
|
||||
else if (config.Servers.Count == 0)
|
||||
throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid");
|
||||
|
||||
@ -244,8 +255,71 @@ namespace IW4MAdmin.Application
|
||||
Running = true;
|
||||
}
|
||||
|
||||
private void HeartBeatThread()
|
||||
{
|
||||
bool successfulConnection = false;
|
||||
restartConnection:
|
||||
while (!successfulConnection)
|
||||
{
|
||||
try
|
||||
{
|
||||
API.Master.Heartbeat.Send(this, true).Wait();
|
||||
successfulConnection = true;
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
successfulConnection = false;
|
||||
Logger.WriteWarning($"Could not connect to heartbeat server - {e.Message}");
|
||||
}
|
||||
|
||||
Thread.Sleep(30000);
|
||||
}
|
||||
|
||||
while (Running)
|
||||
{
|
||||
Logger.WriteDebug("Sending heartbeat...");
|
||||
try
|
||||
{
|
||||
API.Master.Heartbeat.Send(this).Wait();
|
||||
}
|
||||
catch (System.Net.Http.HttpRequestException e)
|
||||
{
|
||||
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
||||
}
|
||||
|
||||
catch (AggregateException e)
|
||||
{
|
||||
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
||||
var exceptions = e.InnerExceptions.Where(ex => ex.GetType() == typeof(RestEase.ApiException));
|
||||
|
||||
foreach (var ex in exceptions)
|
||||
{
|
||||
if (((RestEase.ApiException)ex).StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
{
|
||||
successfulConnection = false;
|
||||
goto restartConnection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (RestEase.ApiException e)
|
||||
{
|
||||
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
||||
if (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
{
|
||||
successfulConnection = false;
|
||||
goto restartConnection;
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(30000);
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Task.Run(() => HeartBeatThread());
|
||||
while (Running || TaskStatuses.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < TaskStatuses.Count; i++)
|
||||
|
@ -630,6 +630,7 @@ namespace IW4MAdmin
|
||||
this.CurrentMap = Maps.Find(m => m.Name == mapname.Value) ?? new Map() { Alias = mapname.Value, Name = mapname.Value };
|
||||
this.MaxClients = maxplayers.Value;
|
||||
this.FSGame = game.Value;
|
||||
this.Gametype = (await this.GetDvarAsync<string>("g_gametype")).Value;
|
||||
|
||||
await this.SetDvarAsync("sv_kickbantime", 60);
|
||||
|
||||
@ -647,7 +648,7 @@ namespace IW4MAdmin
|
||||
CustomCallback = await ScriptLoaded();
|
||||
string mainPath = EventParser.GetGameDir();
|
||||
#if DEBUG
|
||||
// basepath.Value = @"\\192.168.88.253\Call of Duty Black Ops II";
|
||||
basepath.Value = @"\\192.168.88.253\Call of Duty Black Ops II";
|
||||
#endif
|
||||
string logPath = game.Value == string.Empty ?
|
||||
$"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{mainPath}{Path.DirectorySeparatorChar}{logfile.Value}" :
|
||||
|
@ -26,7 +26,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Welcome", "Plugins\Welcome\
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProfanityDeterment", "Plugins\ProfanityDeterment\ProfanityDeterment.csproj", "{958FF7EC-0226-4E85-A85B-B84EC768197D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Login", "Plugins\Login\Login.csproj", "{D9F2ED28-6FA5-40CA-9912-E7A849147AB1}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Login", "Plugins\Login\Login.csproj", "{D9F2ED28-6FA5-40CA-9912-E7A849147AB1}"
|
||||
EndProject
|
||||
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "Master", "Master\Master.pyproj", "{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@ -168,6 +170,22 @@ Global
|
||||
{D9F2ED28-6FA5-40CA-9912-E7A849147AB1}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D9F2ED28-6FA5-40CA-9912-E7A849147AB1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{D9F2ED28-6FA5-40CA-9912-E7A849147AB1}.Release|x86.Build.0 = Release|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|x64.Build.0 = Release|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
166
Master/Master.pyproj
Normal file
166
Master/Master.pyproj
Normal file
@ -0,0 +1,166 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>f5051a32-6bd0-4128-abba-c202ee15fc5c</ProjectGuid>
|
||||
<ProjectHome>.</ProjectHome>
|
||||
<ProjectTypeGuids>{789894c7-04a9-4a11-a6b5-3f4435165112};{1b580a1a-fdb3-4b32-83e1-6407eb2722e6};{349c5851-65df-11da-9384-00065b846f21};{888888a0-9f3d-457c-b088-3a5042f75d52}</ProjectTypeGuids>
|
||||
<StartupFile>runserver.py</StartupFile>
|
||||
<SearchPath>
|
||||
</SearchPath>
|
||||
<WorkingDirectory>.</WorkingDirectory>
|
||||
<LaunchProvider>Web launcher</LaunchProvider>
|
||||
<WebBrowserUrl>http://localhost</WebBrowserUrl>
|
||||
<OutputPath>.</OutputPath>
|
||||
<SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles>
|
||||
<Name>Master</Name>
|
||||
<RootNamespace>Master</RootNamespace>
|
||||
<InterpreterId>MSBuild|dev_env|$(MSBuildProjectFullPath)</InterpreterId>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="master\context\base.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\context\__init__.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\models\instancemodel.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\models\servermodel.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\models\__init__.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Master\resources\authenticate.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Master\resources\instance.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Master\resources\null.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Master\resources\__init__.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\routes.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\schema\instanceschema.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\schema\serverschema.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\schema\__init__.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="runserver.py" />
|
||||
<Compile Include="master\__init__.py" />
|
||||
<Compile Include="master\views.py" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="C:\Projects\IW4M-Admin\Master\master\" />
|
||||
<Folder Include="master\" />
|
||||
<Folder Include="master\context\" />
|
||||
<Folder Include="master\models\" />
|
||||
<Folder Include="master\schema\" />
|
||||
<Folder Include="Master\resources\" />
|
||||
<Folder Include="Master\static\" />
|
||||
<Folder Include="Master\static\content\" />
|
||||
<Folder Include="Master\static\fonts\" />
|
||||
<Folder Include="Master\static\scripts\" />
|
||||
<Folder Include="Master\templates\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="FolderProfile.pubxml" />
|
||||
<Content Include="requirements.txt" />
|
||||
<Content Include="Master\static\content\bootstrap.css" />
|
||||
<Content Include="Master\static\content\bootstrap.min.css" />
|
||||
<Content Include="Master\static\content\site.css" />
|
||||
<Content Include="Master\static\fonts\glyphicons-halflings-regular.eot" />
|
||||
<Content Include="Master\static\fonts\glyphicons-halflings-regular.svg" />
|
||||
<Content Include="Master\static\fonts\glyphicons-halflings-regular.ttf" />
|
||||
<Content Include="Master\static\fonts\glyphicons-halflings-regular.woff" />
|
||||
<Content Include="Master\static\scripts\bootstrap.js" />
|
||||
<Content Include="Master\static\scripts\bootstrap.min.js" />
|
||||
<Content Include="Master\static\scripts\jquery-1.10.2.intellisense.js" />
|
||||
<Content Include="Master\static\scripts\jquery-1.10.2.js" />
|
||||
<Content Include="Master\static\scripts\jquery-1.10.2.min.js" />
|
||||
<Content Include="Master\static\scripts\jquery-1.10.2.min.map" />
|
||||
<Content Include="Master\static\scripts\jquery.validate-vsdoc.js" />
|
||||
<Content Include="Master\static\scripts\jquery.validate.js" />
|
||||
<Content Include="Master\static\scripts\jquery.validate.min.js" />
|
||||
<Content Include="Master\static\scripts\jquery.validate.unobtrusive.js" />
|
||||
<Content Include="Master\static\scripts\jquery.validate.unobtrusive.min.js" />
|
||||
<Content Include="Master\static\scripts\modernizr-2.6.2.js" />
|
||||
<Content Include="Master\static\scripts\respond.js" />
|
||||
<Content Include="Master\static\scripts\respond.min.js" />
|
||||
<Content Include="Master\static\scripts\_references.js" />
|
||||
<Content Include="Master\templates\index.html" />
|
||||
<Content Include="Master\templates\layout.html" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Interpreter Include="dev_env\">
|
||||
<Id>dev_env</Id>
|
||||
<Version>3.6</Version>
|
||||
<Description>dev_env (Python 3.6 (64-bit))</Description>
|
||||
<InterpreterPath>Scripts\python.exe</InterpreterPath>
|
||||
<WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath>
|
||||
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>
|
||||
<Architecture>X64</Architecture>
|
||||
</Interpreter>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.Web.targets" />
|
||||
<!-- Specify pre- and post-build commands in the BeforeBuild and
|
||||
AfterBuild targets below. -->
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
|
||||
<WebProjectProperties>
|
||||
<AutoAssignPort>True</AutoAssignPort>
|
||||
<UseCustomServer>True</UseCustomServer>
|
||||
<CustomServerUrl>http://localhost</CustomServerUrl>
|
||||
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
|
||||
</WebProjectProperties>
|
||||
</FlavorProperties>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}" User="">
|
||||
<WebProjectProperties>
|
||||
<StartPageUrl>
|
||||
</StartPageUrl>
|
||||
<StartAction>CurrentPage</StartAction>
|
||||
<AspNetDebugging>True</AspNetDebugging>
|
||||
<SilverlightDebugging>False</SilverlightDebugging>
|
||||
<NativeDebugging>False</NativeDebugging>
|
||||
<SQLDebugging>False</SQLDebugging>
|
||||
<ExternalProgram>
|
||||
</ExternalProgram>
|
||||
<StartExternalURL>
|
||||
</StartExternalURL>
|
||||
<StartCmdLineArguments>
|
||||
</StartCmdLineArguments>
|
||||
<StartWorkingDirectory>
|
||||
</StartWorkingDirectory>
|
||||
<EnableENC>False</EnableENC>
|
||||
<AlwaysStartWebServerOnDebug>False</AlwaysStartWebServerOnDebug>
|
||||
</WebProjectProperties>
|
||||
</FlavorProperties>
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
17
Master/master/__init__.py
Normal file
17
Master/master/__init__.py
Normal file
@ -0,0 +1,17 @@
|
||||
"""
|
||||
The flask application package.
|
||||
"""
|
||||
|
||||
from flask import Flask
|
||||
from flask_restful import Resource, Api
|
||||
from flask_jwt_extended import JWTManager
|
||||
from master.context.base import Base
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['JWT_SECRET_KEY'] = 'my key!'
|
||||
jwt = JWTManager(app)
|
||||
api = Api(app)
|
||||
ctx = Base()
|
||||
|
||||
import master.routes
|
||||
import master.views
|
1
Master/master/context/__init__.py
Normal file
1
Master/master/context/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
67
Master/master/context/base.py
Normal file
67
Master/master/context/base.py
Normal file
@ -0,0 +1,67 @@
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.interval import IntervalTrigger
|
||||
import time
|
||||
|
||||
class Base():
|
||||
def __init__(self):
|
||||
self.instance_list = {}
|
||||
self.server_list = {}
|
||||
self.token_list = {}
|
||||
self.scheduler = BackgroundScheduler()
|
||||
self.scheduler.start()
|
||||
self.scheduler.add_job(
|
||||
func=self._remove_staleinstances,
|
||||
trigger=IntervalTrigger(seconds=120),
|
||||
id='stale_instance_remover',
|
||||
name='Remove stale instances if no heartbeat in 120 seconds',
|
||||
replace_existing=True
|
||||
)
|
||||
|
||||
def _remove_staleinstances(self):
|
||||
for key, value in list(self.instance_list.items()):
|
||||
if int(time.time()) - value.last_heartbeat > 120:
|
||||
print('[_remove_staleinstances] removing stale instance {id}'.format(id=key))
|
||||
del self.instance_list[key]
|
||||
del self.token_list[key]
|
||||
print('[_remove_staleinstances] {count} active instances'.format(count=len(self.instance_list)))
|
||||
|
||||
def get_server_count(self):
|
||||
return self.server_list.count
|
||||
|
||||
def get_instance_count(self):
|
||||
return self.instance_list.count
|
||||
|
||||
def get_instance(self, id):
|
||||
return self.instance_list[id]
|
||||
|
||||
def instance_exists(self, instance_id):
|
||||
if instance_id in self.instance_list.keys():
|
||||
return instance_id
|
||||
else:
|
||||
False
|
||||
|
||||
def add_instance(self, instance):
|
||||
if instance.id in self.instance_list:
|
||||
print('[add_instance] instance {id} already added, updating instead'.format(id=instance.id))
|
||||
return self.update_instance(instance)
|
||||
else:
|
||||
print('[add_instance] adding instance {id}'.format(id=instance.id))
|
||||
self.instance_list[instance.id] = instance
|
||||
|
||||
def update_instance(self, instance):
|
||||
if instance.id not in self.instance_list:
|
||||
print('[update_instance] instance {id} not added, adding instead'.format(id=instance.id))
|
||||
return self.add_instance(instance)
|
||||
else:
|
||||
print('[update_instance] updating instance {id}'.format(id=instance.id))
|
||||
self.instance_list[instance.id] = instance
|
||||
|
||||
def add_token(self, instance_id, token):
|
||||
print('[add_token] adding {token} for id {id}'.format(token=token, id=instance_id))
|
||||
self.token_list[instance_id] = token
|
||||
|
||||
def get_token(self, instance_id):
|
||||
try:
|
||||
return self.token_list[instance_id]
|
||||
except KeyError:
|
||||
return False
|
1
Master/master/models/__init__.py
Normal file
1
Master/master/models/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
12
Master/master/models/instancemodel.py
Normal file
12
Master/master/models/instancemodel.py
Normal file
@ -0,0 +1,12 @@
|
||||
import time
|
||||
|
||||
class InstanceModel(object):
|
||||
def __init__(self, id, version, uptime, servers):
|
||||
self.id = id
|
||||
self.version = version
|
||||
self.uptime = uptime
|
||||
self.servers = servers
|
||||
self.last_heartbeat = int(time.time())
|
||||
|
||||
def __repr__(self):
|
||||
return '<InstanceModel(id={id})>'.format(id=self.id)
|
14
Master/master/models/servermodel.py
Normal file
14
Master/master/models/servermodel.py
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
class ServerModel(object):
|
||||
def __init__(self, id, port, game, hostname, clientnum, maxclientnum, map, gametype):
|
||||
self.id = id
|
||||
self.port = port
|
||||
self.game = game
|
||||
self.hostname = hostname
|
||||
self.clientnum = clientnum
|
||||
self.maxclientnum = maxclientnum
|
||||
self.map = map
|
||||
self.gametype = gametype
|
||||
|
||||
def __repr__(self):
|
||||
return '<ServerModel(id={id})>'.format(id=self.id)
|
1
Master/master/resources/__init__.py
Normal file
1
Master/master/resources/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
16
Master/master/resources/authenticate.py
Normal file
16
Master/master/resources/authenticate.py
Normal file
@ -0,0 +1,16 @@
|
||||
from flask_restful import Resource
|
||||
from flask import request, jsonify
|
||||
from flask_jwt_extended import create_access_token
|
||||
from master import app, ctx
|
||||
|
||||
|
||||
class Authenticate(Resource):
|
||||
def post(self):
|
||||
instance_id = request.json['id']
|
||||
if ctx.get_token(instance_id) is not False:
|
||||
return { 'message' : 'that id already has a token'}, 401
|
||||
else:
|
||||
token = create_access_token(instance_id)
|
||||
ctx.add_token(instance_id, token)
|
||||
return { 'access_token' : token }, 200
|
||||
|
37
Master/master/resources/instance.py
Normal file
37
Master/master/resources/instance.py
Normal file
@ -0,0 +1,37 @@
|
||||
from flask_restful import Resource
|
||||
from flask import request
|
||||
from flask_jwt_extended import jwt_required
|
||||
from marshmallow import ValidationError
|
||||
from master.schema.instanceschema import InstanceSchema
|
||||
from master import ctx
|
||||
|
||||
class Instance(Resource):
|
||||
def get(self, id=None):
|
||||
if id is None:
|
||||
schema = InstanceSchema(many=True)
|
||||
instances = schema.dump(ctx.instance_list.values())
|
||||
return instances
|
||||
else:
|
||||
try:
|
||||
instance = ctx.get_instance(id)
|
||||
return InstanceSchema().dump(instance)
|
||||
except KeyError:
|
||||
return {'message' : 'instance not found'}, 404
|
||||
|
||||
@jwt_required
|
||||
def put(self, id):
|
||||
try:
|
||||
instance = InstanceSchema().load(request.json)
|
||||
except ValidationError as err:
|
||||
return {'message' : err.messages }, 400
|
||||
ctx.update_instance(instance)
|
||||
return InstanceSchema().dump(instance)
|
||||
|
||||
@jwt_required
|
||||
def post(self):
|
||||
try:
|
||||
instance = InstanceSchema().load(request.json)
|
||||
except ValidationError as err:
|
||||
return err.messages
|
||||
ctx.add_instance(instance)
|
||||
return InstanceSchema().dump(instance)
|
11
Master/master/resources/null.py
Normal file
11
Master/master/resources/null.py
Normal file
@ -0,0 +1,11 @@
|
||||
from flask_restful import Resource
|
||||
from master.models.servermodel import ServerModel
|
||||
from master.schema.serverschema import ServerSchema
|
||||
from master.models.instancemodel import InstanceModel
|
||||
from master.schema.instanceschema import InstanceSchema
|
||||
|
||||
class Null(Resource):
|
||||
def get(self):
|
||||
server = ServerModel(1, 'T6M', 'test', 0, 18, 'mp_test', 'tdm')
|
||||
instance = InstanceModel(1, 1.5, 132, [server])
|
||||
return InstanceSchema().dump(instance)
|
10
Master/master/routes.py
Normal file
10
Master/master/routes.py
Normal file
@ -0,0 +1,10 @@
|
||||
from master import api
|
||||
|
||||
from master.resources.null import Null
|
||||
from master.resources.instance import Instance
|
||||
from master.resources.authenticate import Authenticate
|
||||
|
||||
api.add_resource(Null, '/null')
|
||||
api.add_resource(Instance, '/instance/', '/instance/<string:id>')
|
||||
|
||||
api.add_resource(Authenticate, '/authenticate')
|
1
Master/master/schema/__init__.py
Normal file
1
Master/master/schema/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
29
Master/master/schema/instanceschema.py
Normal file
29
Master/master/schema/instanceschema.py
Normal file
@ -0,0 +1,29 @@
|
||||
from marshmallow import Schema, fields, post_load, validate
|
||||
from master.models.instancemodel import InstanceModel
|
||||
from master.schema.serverschema import ServerSchema
|
||||
|
||||
class InstanceSchema(Schema):
|
||||
id = fields.String(
|
||||
required=True
|
||||
)
|
||||
version = fields.Float(
|
||||
required=True,
|
||||
validate=validate.Range(1.0, 10.0, 'invalid version number')
|
||||
)
|
||||
servers = fields.Nested(
|
||||
ServerSchema,
|
||||
many=True,
|
||||
validate=validate.Length(0, 32, 'invalid server count')
|
||||
)
|
||||
uptime = fields.Int(
|
||||
required=True,
|
||||
validate=validate.Range(0, 2147483647, 'invalid uptime')
|
||||
)
|
||||
last_heartbeat = fields.Int(
|
||||
required=False
|
||||
)
|
||||
|
||||
@post_load
|
||||
def make_instance(self, data):
|
||||
return InstanceModel(**data)
|
||||
|
40
Master/master/schema/serverschema.py
Normal file
40
Master/master/schema/serverschema.py
Normal file
@ -0,0 +1,40 @@
|
||||
from marshmallow import Schema, fields, post_load, validate
|
||||
from master.models.servermodel import ServerModel
|
||||
|
||||
class ServerSchema(Schema):
|
||||
id = fields.Int(
|
||||
required=True,
|
||||
validate=validate.Range(1, 2147483647, 'invalid id')
|
||||
)
|
||||
port = fields.Int(
|
||||
required=True,
|
||||
validate=validate.Range(1, 665535, 'invalid port')
|
||||
)
|
||||
game = fields.String(
|
||||
required=True,
|
||||
validate=validate.Length(1, 8, 'invalid game name')
|
||||
)
|
||||
hostname = fields.String(
|
||||
required=True,
|
||||
validate=validate.Length(1, 48, 'invalid hostname')
|
||||
)
|
||||
clientnum = fields.Int(
|
||||
required=True,
|
||||
validate=validate.Range(0, 128, 'invalid clientnum')
|
||||
)
|
||||
maxclientnum = fields.Int(
|
||||
required=True,
|
||||
validate=validate.Range(1, 128, 'invalid maxclientnum')
|
||||
)
|
||||
map = fields.String(
|
||||
required=True,
|
||||
validate=validate.Length(1, 32, 'invalid map name')
|
||||
)
|
||||
gametype = fields.String(
|
||||
required=True,
|
||||
validate=validate.Length(1, 16, 'invalid gametype')
|
||||
)
|
||||
|
||||
@post_load
|
||||
def make_instance(self, data):
|
||||
return ServerModel(**data)
|
5
Master/master/templates/index.html
Normal file
5
Master/master/templates/index.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% endblock %}
|
44
Master/master/templates/layout.html
Normal file
44
Master/master/templates/layout.html
Normal file
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>IW4MAdmin Master | {{ title }}</title>
|
||||
<link rel="stylesheet" type="text/css" href="/static/content/bootstrap.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/content/site.css" />
|
||||
<script src="/static/scripts/modernizr-2.6.2.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a href="/" class="navbar-brand">Application name</a>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="{{ url_for('home') }}">Home</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container body-content">
|
||||
{% block content %}{% endblock %}
|
||||
<hr />
|
||||
<footer>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="/static/scripts/jquery-1.10.2.js"></script>
|
||||
<script src="/static/scripts/bootstrap.js"></script>
|
||||
<script src="/static/scripts/respond.js"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
17
Master/master/views.py
Normal file
17
Master/master/views.py
Normal file
@ -0,0 +1,17 @@
|
||||
"""
|
||||
Routes and views for the flask application.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from flask import render_template
|
||||
from master import app
|
||||
|
||||
@app.route('/')
|
||||
@app.route('/home')
|
||||
def home():
|
||||
"""Renders the home page."""
|
||||
return render_template(
|
||||
'index.html',
|
||||
title='Home Page',
|
||||
year=datetime.now().year,
|
||||
)
|
16
Master/requirements.txt
Normal file
16
Master/requirements.txt
Normal file
@ -0,0 +1,16 @@
|
||||
aniso8601==3.0.0
|
||||
click==6.7
|
||||
Flask==0.12.2
|
||||
Flask-JWT==0.3.2
|
||||
Flask-JWT-Extended==3.8.1
|
||||
Flask-RESTful==0.3.6
|
||||
itsdangerous==0.24
|
||||
Jinja2==2.10
|
||||
MarkupSafe==1.0
|
||||
marshmallow==3.0.0b8
|
||||
pip==9.0.1
|
||||
PyJWT==1.4.2
|
||||
pytz==2018.4
|
||||
setuptools==39.0.1
|
||||
six==1.11.0
|
||||
Werkzeug==0.14.1
|
9
Master/runserver.py
Normal file
9
Master/runserver.py
Normal file
@ -0,0 +1,9 @@
|
||||
"""
|
||||
This script runs the Master application using a development server.
|
||||
"""
|
||||
|
||||
from os import environ
|
||||
from master import app
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=80, debug=True)
|
@ -16,6 +16,7 @@ namespace SharedLibraryCore.Configuration
|
||||
public string CustomSayName { get; set; }
|
||||
public string DiscordInviteCode { get; set; }
|
||||
public string IPHubAPIKey { get; set; }
|
||||
public string Id { get; set; }
|
||||
public List<ServerConfiguration> Servers { get; set; }
|
||||
public int AutoMessagePeriod { get; set; }
|
||||
public List<string> AutoMessages { get; set; }
|
||||
@ -24,6 +25,7 @@ namespace SharedLibraryCore.Configuration
|
||||
|
||||
public IBaseConfiguration Generate()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString();
|
||||
EnableWebFront = Utilities.PromptBool("Enable webfront");
|
||||
EnableMultipleOwners = Utilities.PromptBool("Enable multiple owners");
|
||||
EnableSteppedHierarchy = Utilities.PromptBool("Enable stepped privilege hierarchy");
|
||||
|
Loading…
Reference in New Issue
Block a user