adding master api project

This commit is contained in:
RaidMax 2018-04-18 15:46:53 -05:00
parent 9aea9e1c02
commit 23ec72e6b6
32 changed files with 769 additions and 4 deletions

4
.gitignore vendored
View File

@ -221,4 +221,6 @@ DEPLOY
global.min.css global.min.css
global.min.js global.min.js
bootstrap-custom.css bootstrap-custom.css
bootstrap-custom.min.css bootstrap-custom.min.css
**/Master/static
**/Master/dev_env

View 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; }
}
}

View 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; }
}
}

View 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);
}
}
}
}

View 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);
}
}

View File

@ -30,6 +30,10 @@
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="RestEase" Version="1.4.5" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\SharedLibraryCore\SharedLibraryCore.csproj"> <ProjectReference Include="..\SharedLibraryCore\SharedLibraryCore.csproj">
<Private>true</Private> <Private>true</Private>

View File

@ -51,7 +51,7 @@ namespace Application.EventParsers
}; };
} }
if (lineSplit[0].Contains("ShutdownGame")) if (lineSplit[0].Contains("ExitLevel"))
{ {
return new GameEvent() return new GameEvent()
{ {
@ -69,6 +69,11 @@ namespace Application.EventParsers
}; };
} }
/*if (lineSplit[0].Contains("ShutdownGame"))
{
}*/
if (lineSplit[0].Contains("InitGame")) if (lineSplit[0].Contains("InitGame"))
{ {
return new GameEvent() return new GameEvent()

View File

@ -65,6 +65,7 @@ namespace IW4MAdmin.Application
{ {
Task.Run(() => WebfrontCore.Program.Init(ServerManager)); Task.Run(() => WebfrontCore.Program.Init(ServerManager));
} }
ServerManager.Start(); ServerManager.Start();
ServerManager.Logger.WriteVerbose("Shutdown complete"); ServerManager.Logger.WriteVerbose("Shutdown complete");

View File

@ -29,6 +29,7 @@ namespace IW4MAdmin.Application
public ILogger Logger { get; private set; } public ILogger Logger { get; private set; }
public bool Running { get; private set; } public bool Running { get; private set; }
public EventHandler<GameEvent> ServerEventOccurred { get; private set; } public EventHandler<GameEvent> ServerEventOccurred { get; private set; }
public DateTime StartTime { get; private set; }
static ApplicationManager Instance; static ApplicationManager Instance;
List<AsyncStatus> TaskStatuses; List<AsyncStatus> TaskStatuses;
@ -60,6 +61,7 @@ namespace IW4MAdmin.Application
ServerEventOccurred += Api.OnServerEvent; ServerEventOccurred += Api.OnServerEvent;
ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings"); ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey); Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
StartTime = DateTime.UtcNow;
} }
private void OnCancelKey(object sender, ConsoleCancelEventArgs args) private void OnCancelKey(object sender, ConsoleCancelEventArgs args)
@ -133,6 +135,15 @@ namespace IW4MAdmin.Application
await ConfigHandler.Save(); 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) else if (config.Servers.Count == 0)
throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid"); throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid");
@ -244,8 +255,71 @@ namespace IW4MAdmin.Application
Running = true; 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() public void Start()
{ {
Task.Run(() => HeartBeatThread());
while (Running || TaskStatuses.Count > 0) while (Running || TaskStatuses.Count > 0)
{ {
for (int i = 0; i < TaskStatuses.Count; i++) for (int i = 0; i < TaskStatuses.Count; i++)

View File

@ -630,6 +630,7 @@ namespace IW4MAdmin
this.CurrentMap = Maps.Find(m => m.Name == mapname.Value) ?? new Map() { Alias = mapname.Value, Name = mapname.Value }; this.CurrentMap = Maps.Find(m => m.Name == mapname.Value) ?? new Map() { Alias = mapname.Value, Name = mapname.Value };
this.MaxClients = maxplayers.Value; this.MaxClients = maxplayers.Value;
this.FSGame = game.Value; this.FSGame = game.Value;
this.Gametype = (await this.GetDvarAsync<string>("g_gametype")).Value;
await this.SetDvarAsync("sv_kickbantime", 60); await this.SetDvarAsync("sv_kickbantime", 60);
@ -647,7 +648,7 @@ namespace IW4MAdmin
CustomCallback = await ScriptLoaded(); CustomCallback = await ScriptLoaded();
string mainPath = EventParser.GetGameDir(); string mainPath = EventParser.GetGameDir();
#if DEBUG #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 #endif
string logPath = game.Value == string.Empty ? string logPath = game.Value == string.Empty ?
$"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{mainPath}{Path.DirectorySeparatorChar}{logfile.Value}" : $"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{mainPath}{Path.DirectorySeparatorChar}{logfile.Value}" :

View File

@ -26,7 +26,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Welcome", "Plugins\Welcome\
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProfanityDeterment", "Plugins\ProfanityDeterment\ProfanityDeterment.csproj", "{958FF7EC-0226-4E85-A85B-B84EC768197D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProfanityDeterment", "Plugins\ProfanityDeterment\ProfanityDeterment.csproj", "{958FF7EC-0226-4E85-A85B-B84EC768197D}"
EndProject 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution 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|x64.Build.0 = Release|Any CPU
{D9F2ED28-6FA5-40CA-9912-E7A849147AB1}.Release|x86.ActiveCfg = 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 {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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

166
Master/Master.pyproj Normal file
View 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
View 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

View File

@ -0,0 +1 @@

View 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

View File

@ -0,0 +1 @@

View 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)

View 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)

View File

@ -0,0 +1 @@

View 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

View 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)

View 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
View 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')

View File

@ -0,0 +1 @@

View 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)

View 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)

View File

@ -0,0 +1,5 @@
{% extends "layout.html" %}
{% block content %}
{% endblock %}

View 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
View 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
View 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
View 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)

View File

@ -16,6 +16,7 @@ namespace SharedLibraryCore.Configuration
public string CustomSayName { get; set; } public string CustomSayName { get; set; }
public string DiscordInviteCode { get; set; } public string DiscordInviteCode { get; set; }
public string IPHubAPIKey { get; set; } public string IPHubAPIKey { get; set; }
public string Id { get; set; }
public List<ServerConfiguration> Servers { get; set; } public List<ServerConfiguration> Servers { get; set; }
public int AutoMessagePeriod { get; set; } public int AutoMessagePeriod { get; set; }
public List<string> AutoMessages { get; set; } public List<string> AutoMessages { get; set; }
@ -24,6 +25,7 @@ namespace SharedLibraryCore.Configuration
public IBaseConfiguration Generate() public IBaseConfiguration Generate()
{ {
Id = Guid.NewGuid().ToString();
EnableWebFront = Utilities.PromptBool("Enable webfront"); EnableWebFront = Utilities.PromptBool("Enable webfront");
EnableMultipleOwners = Utilities.PromptBool("Enable multiple owners"); EnableMultipleOwners = Utilities.PromptBool("Enable multiple owners");
EnableSteppedHierarchy = Utilities.PromptBool("Enable stepped privilege hierarchy"); EnableSteppedHierarchy = Utilities.PromptBool("Enable stepped privilege hierarchy");