rott/kf_sources/XWebAdmin/Classes/UTServerAdmin.uc
2026-07-14 20:27:09 +07:00

1370 lines
40 KiB
Ucode

//==============================================================================
// Web Application to handle remote administration of server
//
// Revised by Micheal Comeau
// Revised by Ron Prestenback
// © 1998-2004, Epic Games, Inc. All Rights Reserved
//==============================================================================
class UTServerAdmin extends WebApplication config;
// TODO:
// Do something with colspan.inc
var config string ActiveSkin;
// Each query handler represents a section in webadmin
var() config array<string> QueryHandlerClasses;
var array<xWebQueryHandler> QueryHandlers;
// Global Objects
var() array<class<WebSkin> > WebSkins; // array of webadmin skins
var() class<UTServerAdminSpectator> SpectatorType;
var() class<WebSkin> DefaultWebSkinClass;
var WebSkin CurrentSkin;// Currently loaded webadmin skin
var UTServerAdminSpectator Spectator; // Used to get console messages
var xAdminUser CurAdmin; // Currently logged admin (not thread safe)
var PlayInfo GamePI; // Contains all confiurable variables
var WebResponse Resp; // Non-thread safe reference to a WebResponse object
// Lists
var array<CacheManager.GameRecord> AllGames;
var array<CacheManager.MapRecord> AllMaps;
var array<CacheManager.MutatorRecord> AllMutators; // All mutators as determined by .int entries
var StringArray AGameType; // All available Game Types
var StringArray AExcMutators; // All available Mutators (Excluded)
var StringArray AIncMutators; // All Mutators currently in play
var StringArray Skins; // All Webadmin custom skins
// Characters which aren't rendered correctly in HTML
struct HtmlChar
{
var string Plain;
var string Coded;
};
var array<HtmlChar> SpecialChars;
var config string DefaultBG; // Non-highlighted items
var config string HighlightedBG; // Active links
// Pages
var config string RootFrame; // This is the master frame divided in 2: Top = Header, bottom = frame page
var config string HeaderPage; // This is the header menu
var config string MessagePage; // Name of the file containing the message template
var config string FramedMessagePage; // Name of file containing message template for sub-frames
var config string RestartPage; // This is the page that users will be transferred to when restarting the server
var string htm;
var config string AdminRealm; // Used by browsers to cache login information
// HTML variables used for skins
var string SkinPath; // Path to use for .htm and .inc content
var string SiteCSSFile; // CSS file to use
var string SiteBG; // Background color for this skin
var string StatusOKColor; // Color of status ok messages
var string StatusErrorColor; // Color of status error messages
// Table cells
var config string CellLeft; // Table cell, left justified
var config string CellCenter; // Table cell, center justified
var config string CellRight; // Table cell, right justified
var config string CellColSpan; // Spanned table cell
var config string NowrapLeft; // Nowrap table cell, left justified
var config string NowrapCenter; // Nowrap table cell, center justified
var config string NowrapRight; // Nowrap table cell, right justified
var config string RowLeft; // Table row, left justified
var config string RowCenter; // Table row, center justified
// Form objects
var config string CheckboxInclude;
var config string TextboxInclude;
var config string SubmitButtonInclude;
var config string RadioButtonInclude;
var config string SelectInclude;
var config string ResetButtonInclude;
var config string HiddenInclude;
var config string SkinSelectInclude;
// Global localization
var localized string Accept;
var localized string Deny;
var localized string Update;
var localized string Custom;
var localized string Error;
var localized string NoneText;
var localized string SwitchText;
var localized string DeleteText;
var localized string NewText;
var localized string Edit;
var localized string WaitTitle;
var localized string MapChanging;
var localized string MapChangingTo;
var localized string AccessDeniedText;
var localized string ErrorAuthenticating;
var localized string NoPrivs;
var localized string LoadingGames, LoadingMaps, Initialized;
var bool bDebug;
// =====================================================================================================================
// =====================================================================================================================
// Initialization
// =====================================================================================================================
// =====================================================================================================================
event Init()
{
Super.Init();
if (SpectatorType != None)
Spectator = Level.Spawn(SpectatorType);
else
Spectator = Level.Spawn(class'UTServerAdminSpectator');
if (Spectator != None)
Spectator.Server = self;
// won't change as long as the server is up and the map hasnt changed
LoadMaps();
LoadGameTypes();
LoadMutators();
LoadQueryHandlers();
ReplaceText( Initialized, "%class%", string(Class) );
ReplaceText( Initialized, "%port%", string(WebServer.ListenPort) );
Log(Initialized,'WebAdmin');
}
function LoadMaps()
{
log(LoadingMaps,'WebAdmin');
class'CacheManager'.static.GetMaplist(AllMaps);
}
function LoadGameTypes()
{
local int i;
Log(LoadingGames,'WebAdmin');
// reinitialize list if needed
AGameType = New(None) class'SortedStringArray';
class'CacheManager'.static.GetGameTypeList(AllGames);
for (i = 0; i < AllGames.Length; i++)
AGameType.Add( AllGames[i].ClassName, AllGames[i].GameName );
}
function LoadMutators()
{
local Mutator M;
local int i, id;
AExcMutators = New(None) class'StringArray';
AIncMutators = New(None) class'SortedStringArray';
if ( Level.IsDemoBuild() )
return;
// Load All mutators
class'CacheManager'.static.GetMutatorList(AllMutators);
for (i = 0; i<AllMutators.Length; i++)
AExcMutators.Add(string(i), AllMutators[i].ClassName);
// Check Current Mutators
for (M = Level.Game.BaseMutator; M != None; M = M.NextMutator)
{
if (M.bUserAdded)
{
id = AExcMutators.FindTagId(String(M.Class));
if (id >= 0)
AIncMutators.Add(AExcMutators.GetItem(id), AllMutators[id].FriendlyName);
else
log("Unknown Mutator in use: "@M.Class,'WebAdmin');
}
}
}
function LoadQueryHandlers()
{
local int i, j;
local xWebQueryHandler QH;
local class<xWebQueryHandler> QHC;
LoadSkins();
for (i=0; i<QueryHandlerClasses.Length; i++)
{
QHC = class<xWebQueryHandler>(DynamicLoadObject(QueryHandlerClasses[i],class'Class'));
// skip invalid classes
if (QHC != None)
{
// Make sure we dont have duplicate instance of the same class
for (j=0;j<QueryHandlers.Length; j++)
{
if (QueryHandlers[j].Class == QHC)
{
QHC = None;
break;
}
}
if (QHC != None)
{
QH = new(self) QHC;
if (QH != None)
{
if (QH.Init())
{
QueryHandlers.Length = QueryHandlers.Length+1;
QueryHandlers[QueryHandlers.Length - 1] = QH;
}
else Log("WebQueryHandler:"@QHC@"could not be initialized",'WebAdmin');
}
}
}
else
{
log("Invalid QueryHandlerClass:"$QueryHandlerClasses[i]$". Removing invalid entry.",'WebAdmin');
QueryHandlerClasses.Remove(i,1);
SaveConfig();
}
}
}
function string SetGamePI(optional string GameType)
{
local class<Info> TmpClass;
local array<class<Info> > InfoClasses;
local int i;
if (GameType == "")
{
InfoClasses[0] = Level.Game.Class;
GameType = string(Level.Game.Class);
}
else
{
TmpClass = class<Info>(DynamicLoadObject(GameType, class'Class'));
if (TmpClass == None)
{
Log("Error loading gametype"@GameType,'WebAdmin');
return "";
}
InfoClasses[0] = TmpClass;
}
if (GamePI == None)
GamePI = new(None) class'PlayInfo';
if (Level.Game.AccessControl != None)
InfoClasses[1] = Level.Game.AccessControl.Class;
while (i < AIncMutators.Count())
{
TmpClass = class<Info>(DynamicLoadObject(AllMutators[int(AIncMutators.GetItem(i++))].ClassName, class'Class'));
if (TmpClass != None)
InfoClasses[InfoClasses.Length] = TmpClass;
}
GamePI.Init( InfoClasses );
return GameType;
}
// =====================================================================================================================
// =====================================================================================================================
// List Initialization & Generation
// =====================================================================================================================
// =====================================================================================================================
// Returns a list of all maps for this gametype
function StringArray ReloadExcludeMaps(String GameType)
{
local int i, GameIndex;
local string MapPrefix;
local StringArray AMaps;
local array<CacheManager.MapRecord> MapRecords;
if ( GameType == "" )
GameType = string(Level.Game.Class);
GameIndex = GetGameCacheIndex(GameType);
if ( GameIndex == -1 )
{
Warn("Could not load gametype"@GameType@"for maplist.");
return AMaps;
}
AMaps = New(None) class'SortedStringArray';
MapPrefix = AllGames[GameIndex].MapPrefix;
class'CacheManager'.static.GetMapList(MapRecords, MapPrefix);
for ( i = 0; i < MapRecords.Length; i++ )
if ( ValidMap(MapRecords[i].MapName) )
{
if ( bDebug )
log("Adding entry to exclude maplist Item '"$i$"' Tag '"$MapRecords[i].MapName$"'");
AMaps.Add(MapRecords[i].MapName, MapRecords[i].MapName, True);
}
return AMaps;
}
// Returns a list of active maps for this gametype
function StringArray ReloadIncludeMaps(StringArray ExMaps, int GameIndex, int MapListIndex)
{
local int i, id;
local string MapName;
local array<string> Maps;
local StringArray Arr;
Arr = new(None) class'StringArray';
Maps = Level.Game.MaplistHandler.GetMapList(GameIndex, MapListIndex);
for ( i = 0; i < Maps.Length; i++ )
{
MapName = class'MaplistRecord'.static.GetBaseMapName(Maps[i]);
Arr.Add( MapName $ Maps[i], MapName, True );
if ( bDebug )
log("Adding map entry to include list Item '"$MapName $ Maps[i]$"' Tag '"$MapName$"'");
// Always add the maps that are in the existing maplist, even if they aren't valid maps
id = ExMaps.FindTagId(MapName);
if ( id != -1 )
ExMaps.Remove(id);
}
return Arr;
}
// Only 'Mutators' has value when function is called
function CreateFullMutatorList(out StringArray Mutators, out StringArray GroupsOnly)
{
local StringArray Grouped;
local int i,j,z;
local string GrpName;
local string thisgroup, nextgroup;
// This class provides a specialized sorting function
// since mutators may be grouped - mutators in the same groups
// are not allowed to be selected together
Grouped = new(None) class'SortedStringArray';
// Create array sorted on GroupName...this allows to flag
// the mutator for a different .inc file (radio instead of checkbox)
for (i = 0; i < Mutators.Count(); i++)
{
j = int(Mutators.GetItem(i));
// If the mutator author forgot to configure a group name for the mutator,
// generate a groupname "Z" + number, so that every mutator without group name
// has its own group, and isn't grouped together
if (AllMutators[j].GroupName == "")
GrpName = "Z" $ string (z++);
else GrpName = AllMutators[j].GroupName;
Grouped.Add(string(j),GrpName $ "." $ AllMutators[j].FriendlyName $ AllMutators[j].ClassName);
}
// Move all grouped mutators to GroupsOnly StringArray for sorting by friendly name
for (i = 0; i < Grouped.Count(); i++)
{
thisgroup = AllMutators[int(Grouped.GetItem(i))].GroupName;
nextgroup = "";
if (thisgroup == "") continue;
if (i+1 < Grouped.Count())
nextgroup = AllMutators[int(Grouped.GetItem(i+1))].GroupName;
if (thisgroup ~= nextgroup)
{
j=i;
while(nextgroup ~= thisgroup && j < Grouped.Count())
{
GroupsOnly.MoveFromId(Grouped, Grouped.FindItemId(Grouped.GetItem(j)));
thisgroup = nextgroup;
if (j+1 == Grouped.Count())
nextgroup="";
else nextgroup = AllMutators[int(Grouped.GetItem(j+1))].GroupName;
}
if (j < Grouped.Count())
GroupsOnly.MoveFromId(Grouped,Grouped.FindItemId(Grouped.GetItem(j)));
i=-1;
}
}
// Move all non-grouped mutators back to Mutators StringArray
// for re-sorting by Friendly Name
Mutators.Reset();
for (i=0;i<Grouped.Count();i++)
{
j=int(Grouped.GetItem(i));
Mutators.Add(string(j),AllMutators[j].FriendlyName);
}
Grouped.Reset();
}
// =====================================================================================================================
// =====================================================================================================================
// Server Management & Control
// =====================================================================================================================
// =====================================================================================================================
function LoadSkins()
{
local int i;
local string S;
local class<WebSkin> SkinClass;
Skins = new(None) class'StringArray';
S = Level.GetNextInt("XWebAdmin.WebSkin", i++);
while (S != "")
{
SkinClass = class<WebSkin>(DynamicLoadObject(S, class'Class'));
if (SkinClass != None)
{
Skins.Add(Level.GetItemName(string(SkinClass)), SkinClass.default.DisplayName);
WebSkins[WebSkins.Length] = SkinClass;
}
S = Level.GetNextInt("XWebAdmin.WebSkin", i++);
}
ApplySkinSettings();
}
function ApplySkinSettings()
{
local int i;
if (Skins == None)
return;
if (CurrentSkin != None && Level.GetItemName(string(CurrentSkin.Class)) ~= ActiveSkin)
return;
i = Skins.FindItemId(ActiveSkin);
if (i < 0)
CurrentSkin = new(None) class'XWebAdmin.ROOstSkin';
else CurrentSkin = new(None) WebSkins[i];
if (CurrentSkin != None)
CurrentSkin.Init(Self);
}
function ServerChangeMap(WebRequest Request, WebResponse Response, string MapName, string GameType)
{
local int i;
local bool bConflict;
local string Conflicts, Str, ShortName, Muts;
if (Level.NextURL != "")
{
ShowMessage(Response, WaitTitle, MapChanging);
}
if (Request.GetVariable("Save", "") != "")
{
// All we need to do is override settings as required
for (i = 0; i<GamePI.Settings.Length; i++)
{
ShortName = Level.GetItemName(GamePI.Settings[i].SettingName);
if (Request.GetVariable(GamePI.Settings[i].SettingName, "") != "")
Level.UpdateURL(ShortName, GamePI.Settings[i].Value, false);
}
}
else
{
bConflict = false;
Conflicts = "";
// Make sure we have a GamePI with the right GameType selected
GameType = SetGamePI(GameType);
// Check each parameter and see if it conflicts with the settings on the command line
for (i = 0; i<GamePI.Settings.Length; i++)
{
// Hack to get around "AdminName bug"
if (HasURLOption(GamePI.Settings[i].SettingName, Str) && !(GamePI.Settings[i].Value ~= Str) && GamePI.Settings[i].SettingName != "GameReplicationInfo.AdminName")
{
// We have a conflicting setting, prepare a table row for it.
Response.Subst("SettingName", GamePI.Settings[i].SettingName);
Response.Subst("SettingText", GamePI.Settings[i].DisplayName);
Response.Subst("DefVal", GamePI.Settings[i].Value);
Response.Subst("URLVal", Str);
Response.Subst("MapName", MapName);
Response.Subst("GameType", GameType);
Conflicts = Conflicts $ WebInclude(RestartPage$"_row");//skinme
bConflict = true;
}
}
if (bConflict)
{
// Conflicts exist .. show the RestartPage
Response.Subst("Conflicts", Conflicts);
Response.Subst("PostAction", RestartPage);
Response.Subst("Section", "Restart Conflicts");
Response.Subst("SubmitValue", Accept);
ShowPage(Response, RestartPage);
return;
}
}
Muts = UsedMutators();
if (Muts != "")
Muts = "?Mutator=" $ Muts;
Level.ServerTravel(MapName$"?Game="$GameType$Muts, false);
ShowMessage(Response, WaitTitle, MapChanging);
}
// Save custom maplist
function UpdateCustomMapList(int GameIndex, int Index, string NewName)
{
if ( !(Level.Game.MaplistHandler.GetMapListTitle(GameIndex, Index) == NewName) )
Index = Level.Game.MaplistHandler.RenameList(GameIndex, Index, NewName);
if ( Level.Game.MaplistHandler.GetActiveList(GameIndex) == Index )
Level.Game.MaplistHandler.ApplyMaplist(GameIndex, Index);
else Level.Game.MaplistHandler.SaveMapList(GameIndex, Index);
}
// Called at end of game
function CleanupApp()
{
local int i;
if (Spectator != None)
Spectator = None;
for (i = 0; i < QueryHandlers.Length; i++)
QueryHandlers[i].Cleanup();
// In case a query is hung
if (Resp != None)
CleanupQuery();
Super.CleanupApp(); // Always call Super.CleanupApp();
}
// =====================================================================================================================
// =====================================================================================================================
// Webpage Query Response Handling
// =====================================================================================================================
// =====================================================================================================================
function bool PreQuery(WebRequest Request, WebResponse Response)
{
local bool bResult;
local int i;
if (Level == None || Level.Game == None || Level.Game.AccessControl == None)
{
ShowMessage(Response, Error, ErrorAuthenticating);
return false;
}
// Prevent admin credentials from being stored across threads
// Also ensure that hanging connections are closed before beginning another Query()
if (Resp != None)
CleanupQuery();
if (Spectator == None)
{
if (SpectatorType != None)
Spectator = Level.Spawn(SpectatorType);
else Spectator = Level.Spawn(class'UTServerAdminSpectator');
if (Spectator != None)
Spectator.Server = self;
}
if (Spectator == None)
{
ShowMessage(Response, Error, ErrorAuthenticating);
return false;
}
// Check authentication:
if (!Level.Game.AccessControl.AdminLogin(Spectator, Request.Username, Request.Password))
{
Response.FailAuthentication(AdminRealm);
return false;
}
CurAdmin = Level.Game.AccessControl.GetLoggedAdmin(Spectator);
if (CurAdmin == None)
{
ShowMessage(Response, Error, ErrorAuthenticating);
Level.Game.AccessControl.AdminLogout(Spectator);
return false;
}
Resp = Response;
bResult = True;
for (i=0; i<QueryHandlers.Length; i++)
{
if (!QueryHandlers[i].PreQuery(Request, Response))
bResult = False;
}
return bResult;
}
event Query(WebRequest Request, WebResponse Response)
{
local int i;
Response.Subst("BugAddress", "webadminbugs"$Level.EngineVersion$"@epicgames.com");
Response.Subst("CSS", SiteCSSFile);
Response.Subst("BODYBG", SiteBG);
// Match query function. checks URI and calls appropriate input/output function
if (CurrentSkin != None && CurrentSkin.SpecialQuery.Length > 0)
{
for (i = 0; i < CurrentSkin.SpecialQuery.Length; i++)
{
if (CurrentSkin.SpecialQuery[i] ~= Mid(Request.URI,1))
{
if (CurrentSkin.HandleSpecialQuery(Request, Response))
return;
break;
}
}
}
// Check whether we're supposed to handle this query
switch (Mid(Request.URI, 1))
{
case "":
case RootFrame: QueryRootFrame(Request, Response); return;
case HeaderPage: QueryHeaderPage(Request, Response); return;
case RestartPage: if (!MapIsChanging()) QuerySubmitRestartPage(Request, Response); return;
case SiteCSSFile: Response.SendCachedFile( Path $ SkinPath $ "/" $ Mid(Request.URI, 1), "text/css"); return;
}
// If not, allow each query handler a chance to process this query. Show error message if no query handlers
// were able to handle the query
for (i=0; i<QueryHandlers.Length; i++)
if (QueryHandlers[i].Query(Request, Response))
return;
ShowMessage(Response, Error, "Page not found!");
return;
}
event PostQuery(WebRequest Request, WebResponse Response)
{
local int i;
for (i=0; i<QueryHandlers.Length; i++)
if (!QueryHandlers[i].PostQuery(Request, Response))
Response.Connection.bDelayCleanup = True;
if (Response.Connection.IsHanging())
return;
CleanupQuery();
}
function CleanupQuery()
{
if (Resp != None && Resp.Connection.IsHanging())
Resp.Connection.Timer();
Resp = None;
CurAdmin = None;
Level.Game.AccessControl.AdminLogout(Spectator);
}
// Generates the HTML for the main root frame
function QueryRootFrame(WebRequest Request, WebResponse Response)
{
local String GroupPage;
if (QueryHandlers.Length > 0)
GroupPage = QueryHandlers[0].DefaultPage;
if (Request.GetVariable("ChangeSkin") != "")
{
ActiveSkin = Request.GetVariable("WebSkin", ActiveSkin);
ApplySkinSettings();
SaveConfig();
}
GroupPage = Request.GetVariable("Group", GroupPage);
Response.Subst("HeaderURI", HeaderPage$"?Group="$GroupPage);
Response.Subst("BottomURI", GroupPage);
Response.Subst("ServerName", class'GameReplicationInfo'.default.ServerName);
ShowFrame(Response, RootFrame);
}
// Generates the HTML for the top frame, which displays the available areas of webadmin and webadmin skin selector
function QueryHeaderPage(WebRequest Request, WebResponse Response)
{
local int i;
local string menu, GroupPage, Dis, CurPageTitle;
Response.Subst("AdminName", CurAdmin.UserName);
Response.Subst("HeaderColSpan", "2");
if (QueryHandlers.Length > 0)
{
GroupPage = Request.GetVariable("Group", QueryHandlers[0].DefaultPage);
// We build a multi-column table for each QueryHandler
menu = "";
CurPageTitle = "";
for (i=0; i<QueryHandlers.Length; i++)
{
if (QueryHandlers[i].DefaultPage == GroupPage)
CurPageTitle = QueryHandlers[i].Title;
Dis = "";
if (QueryHandlers[i].NeededPrivs != "" && !CanPerform(QueryHandlers[i].NeededPrivs))
Dis = "d";
Response.Subst("MenuLink", RootFrame$"?Group="$QueryHandlers[i].DefaultPage);
Response.Subst("MenuTitle", QueryHandlers[i].Title);
menu = menu$WebInclude(HeaderPage$"_item"$Dis);//skinme
}
Response.Subst("Location", CurPageTitle);
Response.Subst("HeaderMenu", menu);
}
if ( CanPerform("Xs") )
{
Response.Subst("HeaderColSpan", "3");
Response.Subst("SkinSelect", Select("WebSkin", GenerateSkinSelect()));
Response.Subst("WebSkinSelect", WebInclude(SkinSelectInclude));
}
// Set URIs
ShowPage(Response, HeaderPage);
}
function QueryRestartPage(WebRequest Request, WebResponse Response)
{
if ( CanPerform("Mr|Mt|Mm|Ms|Mu|Ml") )
ServerChangeMap(Request, Response, Level.GetURLMap(), String(Level.Game.Class));
}
function QuerySubmitRestartPage(WebRequest Request, WebResponse Response)
{
if ( CanPerform("Mr|Mt|Mm|Ms|Mu|Ml") )
ServerChangeMap(Request, Response, Request.GetVariable("MapName"), Request.GetVariable("GameType"));
}
// Generates HTML indicating that the admin doesn't have the priviledges required to view this page
function AccessDenied(WebResponse Response)
{
ShowMessage(Response, AccessDeniedText, NoPrivs);
}
// =====================================================================================================================
// =====================================================================================================================
// HTML Generators for combo lists
// =====================================================================================================================
// =====================================================================================================================
// HTML for gametype combo options
function string GenerateGameTypeOptions(String CurrentGameType)
{
local int i;
local string SelectedStr, OptionStr;
for (i=0; i < AGameType.Count(); i++)
{
if (CurrentGameType ~= AGameType.GetItem(i))
SelectedStr = " selected";
else
SelectedStr = "";
OptionStr = OptionStr$"<option value=\""$AGameType.GetItem(i)$"\""$SelectedStr$">"$AGameType.GetTag(i)$"</option>";
}
return OptionStr;
}
// HTML for active mutator selection list
function string GenerateMutatorOptions(String SelectedMutators)
{
local int i, j;
local string SelectedStr, OptionStr;
local array<string> MutatorList;
local StringArray SortedMutatorList;
local bool bNoMutators;
Split( SelectedMutators, ",", MutatorList);
SortedMutatorList = new(None) class'SortedStringArray';
for(i=0; i < AllMutators.Length; i++)
SortedMutatorList.Add(AllMutators[i].ClassName,AllMutators[i].FriendlyName);
// Search mutator list for NONE, if found then no other mutators can be selected.
SelectedStr = "";
for( j=0; j < MutatorList.Length; j++)
{
if( MutatorList[j] ~= "NONE" )
{
SelectedStr = " selected";
bNoMutators = true;
break;
}
}
// add None options - for unloading all mutators
OptionStr = "<option value=\"NONE\"" $ SelectedStr $ ">" $ NoneText $ "</option>";
for(i=0; i < SortedMutatorList.Count(); i++)
{
SelectedStr = "";
if( !bNoMutators ) // dont select other mutators if none selected
{
for( j=0; j < MutatorList.Length; j++)
{
if( MutatorList[j] ~= SortedMutatorList.GetItem(i) )
{
SelectedStr = " selected";
break;
}
}
}
OptionStr $= "<option value=\"" $ SortedMutatorList.GetItem(i) $ "\"" $ SelectedStr $ ">" $
SortedMutatorList.GetTag(i) $ "</option>";
}
SortedMutatorList.Reset();
return OptionStr;
}
// HTML for maplist selection options
function string GenerateMapListOptions(string GameType, int Active)
{
local int i, idx;
local array<string> Ar;
local string Result, selected;
idx = Level.Game.MaplistHandler.GetGameIndex(GameType);
Ar = Level.Game.MaplistHandler.GetMapListNames(idx);
for (i = 0; i < Ar.Length; i++)
{
selected =Eval(i == Active, " selected", "");
Result $= "<option value=\"" $ string(i) $ "\"" $ selected $ ">" $ Ar[i] $ "</option>";
}
return Result;
}
// HTML for the dual map selection lists
function string GenerateMapListSelect(StringArray MapList, StringArray MovedMaps)
{
local int i;
local String ResponseStr, SelectedStr;
if (MapList.Count() == 0)
return "<option value=\"\">***"@NoneText@"***</option>";
for (i = 0; i<MapList.Count(); i++)
{
if ( ValidMap(MapList.GetTag(i)))
{
SelectedStr = "";
if (MovedMaps != None && MovedMaps.FindTagId(MapList.GetTag(i)) >= 0)
SelectedStr = " selected";
ResponseStr = ResponseStr$"<option value=\""$MapList.GetItem(i)$"\""$SelectedStr$">"$MapList.GetTag(i)$"</option>";
}
}
return ResponseStr;
}
// HTML for webadmin skin options
function string GenerateSkinSelect()
{
local string S, selectedstring;
local int i;
if (Skins.Count() == 0)
return "<option value=\"\">***"@NoneText@"***</option>";
for (i = 0; i < Skins.Count(); i++)
{
SelectedString = Eval(CurrentSkin != None && Level.GetItemName(string(CurrentSkin.Class)) ~= Skins.GetItem(i), " selected", "");
S = S $ "<option value=\""$Skins.GetItem(i)$"\""$SelectedString$">"$Skins.GetTag(i)$"</option>";
}
return S;
}
// =====================================================================================================================
// =====================================================================================================================
// HTML helpers
// =====================================================================================================================
// =====================================================================================================================
// Replaces special characters in a regular string with HTML
static function string HtmlEncode(string src)
{
local int i;
for (i = 0; i < default.SpecialChars.Length; i++)
src = Repl(src, default.SpecialChars[i].Plain, default.SpecialChars[i].Coded);
return src;
}
// Replaces HTML special characters with regular text
static function string HtmlDecode(string src)
{
local int i;
for (i = 0; i < default.SpecialChars.Length; i++)
src = Repl(src, default.SpecialChars[i].Coded, default.SpecialChars[i].Plain);
return src;
}
// Make the specified string's length equal to MaxWidth by adding PadString to the beginning of the string
function string PadLeft( string Text, int MaxWidth, optional string PadString)
{
local String OutStr;
if (PadString == "")
PadString = " ";
for (OutStr = Text; Len(OutStr) < MaxWidth; OutStr = PadString $ OutStr);
return Right(OutStr, MaxWidth); // in case PadStr is more than one character
}
// Make the specified string's length equal to MaxWidth by adding PadString to the end of the string
function string PadRight(string Text, int MaxWidth, optional string PadString)
{
local string OutStr;
if (PadString == "")
PadString = " ";
for (OutStr = Text; Len(OutStr) < MaxWidth; OutStr $= PadString);
return Left(OutStr, MaxWidth);
}
// Adds the HTML necessary for showing the current map's name
function MapTitle(WebResponse Response)
{
local string str, smap;
str = Level.Game.GameReplicationInfo.GameName$" in ";
if (Level.Title ~= "untitled")
{
smap = Level.GetURLMap();
if (Right(smap, 4) ~= ".ut2")
str $= Left(smap, Len(smap) - 4);
else
str $= smap;
}
else
str $= Level.Title;
Response.Subst("SubTitle", str);
}
function bool MapIsChanging()
{
if (Level.NextURL != "")
{
ShowMessage(Resp, WaitTitle, MapChanging);
return true;
}
return false;
}
// Converts the specified text into a hyperlink
function string HyperLink(string url, string text, bool bEnabled, optional string target)
{
local string hlink;
if (bEnabled)
{
hlink = "<a href='"$url$"'";
if (target != "")
hlink = hlink@"target='"$target$"'";
hlink = hlink$">"$text$"</a>";
return hlink;
}
return text;
}
// Loads an .inc file into the current WebResponse object
function string WebInclude(string file)
{
local string S;
if (CurrentSkin != None)
{
S = CurrentSkin.HandleWebInclude(Resp, file);
if (S != "") return S;
}
return Resp.LoadParsedUHTM(Path $ SkinPath $ "/" $ file $ ".inc");
}
function bool ShowFrame(WebResponse Response, string Page)
{
if (CurrentSkin != None && CurrentSkin.HandleHTM(Response, Page))
return true;
Response.IncludeUHTM( Path $ SkinPath $ "/" $ Page $ htm);
return true;
}
function bool ShowPage(WebResponse Response, string Page)
{
if (CurrentSkin != None && CurrentSkin.HandleHTM(Response, Page))
{
Response.ClearSubst();
return true;
}
Response.IncludeUHTM( Path $ SkinPath $ "/" $ Page $ htm);
Response.ClearSubst();
return true;
}
function StatusError(WebResponse Response, string Message)
{
if (Left(Message,1) == "@")
Message = Mid(Message,1);
Response.Subst("Status", "<font color='"$StatusErrorColor$"'>"$Message$"</font><br>");
}
function StatusOk(WebResponse Response, string Message)
{
Response.Subst("Status", "<font color='"$StatusOKColor$"'>"$Message$"</font>");
}
function bool StatusReport(WebResponse Response, string ErrorMessage, string SuccessMessage)
{
if (ErrorMessage == "")
StatusOk(Response, SuccessMessage);
else
StatusError(Response, ErrorMessage);
return ErrorMessage=="";
}
function ShowMessage(WebResponse Response, string Title, string Message)
{
if (CurrentSkin != None && CurrentSkin.HandleMessagePage(Response, Title, Message))
return;
Response.Subst("Section", Title);
Response.Subst("Message", Message);
Response.IncludeUHTM(Path $ SkinPath $ "/" $ MessagePage $ htm);
}
// Framed message page (eliminates double headers)
function ShowFramedMessage(WebResponse Response, string Message, bool bIsErrorMsg)
{
if (CurrentSkin != None && CurrentSkin.HandleFrameMessage(Response, Message, bIsErrorMsg))
return;
if (bIsErrorMsg)
StatusError(Response,Message);
else Response.Subst("Message", Message);
Response.IncludeUHTM(Path $ SkinPath $ "/" $ FramedMessagePage $ htm);
}
// =====================================================================================================================
// =====================================================================================================================
// Form control HTML helpers
// =====================================================================================================================
// =====================================================================================================================
// Creates a checkbox, optionally greying it out (disabled)
function string Checkbox(string tag, bool bChecked, optional bool bDisabled)
{
Resp.Subst("CheckName", tag);
Resp.Subst("Checked", Eval(bChecked, " checked", ""));
Resp.Subst("Disabled", Eval(bDisabled, " disabled", ""));
return WebInclude(CheckboxInclude);
}
function string Hidden(string Tag, string Value)
{
Resp.Subst("HiddenName", Tag);
Resp.Subst("HiddenValue", Value);
return WebInclude(HiddenInclude);
}
// Creates a submit button
function string SubmitButton(string SubmitButtonName, string SubmitButtonValue)
{
Resp.Subst("SubmitName", SubmitButtonName);
Resp.Subst("SubmitValue", SubmitButtonValue);
return WebInclude(SubmitButtonInclude);
}
function string ResetButton(string ResetButtonName, string ResetButtonValue)
{
Resp.Subst("ResetName", ResetButtonName);
Resp.Subst("ResetValue", ResetButtonValue);
return WebInclude(ResetButtonInclude);
}
// Creates a textbox, optionally providing a default value
function string TextBox(string TextName, coerce string Size, coerce string MaxLength, optional string DefaultValue)
{
Resp.Subst("TextName", TextName);
if (int(Size) > 0) Resp.Subst("TextSize", "size=\""$Size$"\"");
else Resp.Subst("TextSize", "");
if (int(MaxLength) > 0) Resp.Subst("TextLength", "maxlength=\""$MaxLength$"\"");
else Resp.Subst("TextLength", "");
Resp.Subst("TextValue", DefaultValue);
return WebInclude(TextboxInclude);
}
function string RadioButton(string Group, string Value, bool bSelected)
{
Resp.Subst("RadioGroup", Group);
Resp.Subst("RadioValue", Value);
Resp.Subst("Selected", Eval(bSelected, " checked", ""));
return WebInclude(RadioButtonInclude);
}
function string Select(string SelectName, string SelectOptions)
{
Resp.Subst("SelectName", SelectName);
Resp.Subst("ListOptions", SelectOptions);
return WebInclude(SelectInclude);
}
// =====================================================================================================================
// =====================================================================================================================
// Accessor / Utility Functions
// =====================================================================================================================
// =====================================================================================================================
function int GetGameCacheIndex( string GameClass )
{
local int i;
for ( i = 0; i < AllGames.Length; i++ )
if ( AllGames[i].ClassName ~= GameClass )
return i;
return -1;
}
// Returns whether this map is a valid map
function bool ValidMap(string MapURL)
{
local int i;
local string MapName;
if ( Left(Caps(MapName),4) ~= "TUT-" )
return false;
MapName = class'MaplistRecord'.static.GetBaseMapName(MapURL);
for ( i = 0; i < AllMaps.Length; i++ )
{
if ( AllMaps[i].MapName ~= MapName )
return true;
}
return false;
}
function FormatMapName(out string FullName, out string ShortName)
{
local string ext;
ext = ".ut2";
if (FullName == "" && ShortName == "") return;
if (FullName != "" && ShortName == "")
{
if (Right(FullName, 4) ~= ext)
ShortName = Left(FullName, Len(FullName) - 4);
else
{
ShortName = FullName;
FullName = FullName $ ext;
}
}
else if (FullName == "" && ShortName != "")
{
if (Right(ShortName,4) ~= ext)
{
FullName = ShortName;
ShortName = Left(ShortName, Len(ShortName) - 4);
}
else
FullName = ShortName $ ext;
}
else
{
if (!(Right(FullName,4) ~= ext))
FullName = FullName $ ext;
ShortName = Left(FullName, Len(FullName) - 4);
}
}
function String UsedMutators()
{
local int i;
local String OutStr;
while (i < AIncMutators.Count())
{
if (OutStr != "") OutStr $= ",";
OutStr $= AllMutators[int(AIncMutators.GetItem(i++))].ClassName;
}
return OutStr;
}
function bool HasURLOption(string ParamName, out string Value)
{
local string Param;
local int i;
Param = ParamName;
while (true)
{
i = Instr(Param, ".");
if (i < 0)
break;
Param = Mid(Param, i+1);
}
Value = Level.GetUrlOption(Param);
return Value != "";
}
function string NextPriv(out string PrivString)
{
local int pos;
local string Priv;
pos = Instr(PrivString, "|");
if (pos < 0)
pos = Len(PrivString);
EatStr(Priv, PrivString, Pos);
if (PrivString != "") // Remove leading pipe char
PrivString = Mid(PrivString, 1);
return Priv;
}
function bool CanPerform(string privs)
{
local string priv;
priv = NextPriv(privs);
while (priv != "")
{
if (Level.Game.AccessControl.AllowPriv(priv) && CurAdmin.HasPrivilege(priv))
return true;
priv = NextPriv(privs);
}
return false;
}
defaultproperties
{
SpectatorType=class'XWebAdmin.UTServerAdminSpectator'
DefaultWebSkinClass=class'XWebAdmin.ROOstSkin'
QueryHandlerClasses(0)="XWebAdmin.xWebQueryCurrent"
QueryHandlerClasses(1)="XWebAdmin.xWebQueryDefaults"
QueryHandlerClasses(2)="XWebAdmin.xWebQueryAdmins"
RootFrame="rootframe"
HeaderPage="mainmenu"
MessagePage="message"
RestartPage="server_restart"
DefaultBG="#aaaaaa"
HighlightedBG="#3a7c8c"
SiteBG="#411b17"//"#243954"
AdminRealm="KF Remote Admin Server"
htm=".htm"
SpecialChars(0)=(Plain="&",Coded="&amp;")
SpecialChars(1)=(Plain="\"",Coded="&quot;")
SpecialChars(2)=(Plain=" ",Coded="&nbsp;")
SpecialChars(3)=(Plain="<",Coded="&lt;")
SpecialChars(4)=(Plain=">",Coded="&gt;")
SpecialChars(5)=(Plain="©",Coded="&copy;")
SpecialChars(6)=(Plain="™",Coded="&#8482;")
SpecialChars(7)=(Plain="®",Coded="&reg;")
StatusOKColor="#33cc66"
StatusErrorColor="Yellow"
Accept="Accept"
Deny="Deny"
Update="Update"
Custom="Custom"
Error="Error"
NoneText="None"
SwitchText="Switch"
DeleteText="Delete"
NewText="New"
Edit="Edit"
AccessDeniedText="Access Denied"
ErrorAuthenticating="Exception Occurred During Authentication!"
NoPrivs="Your privileges are not sufficient to view this page."
WaitTitle="Please Wait"
MapChanging="The server is now switching maps. Please allow 10 - 15 seconds while the server changes maps."
MapChangingTo="The server is now switching to map '%MapName%'. Please allow 10-15 seconds while the server changes maps."
LoadingGames="Loading Game Types"
LoadingMaps="Loading Available Maps"
Initialized="%class% Initialized on port %port%"
FramedMessagePage="frame_message"
SiteCSSFile="ROOst.css"
CheckboxInclude="checkbox"
TextboxInclude="textbox"
RadioButtonInclude="radio_button"
SelectInclude="select"
SubmitButtonInclude="submit_button"
ResetButtonInclude="reset_button"
HiddenInclude="hidden"
SkinSelectInclude="mainmenu_items"
CellLeft="cell_left"
CellCenter="cell_center"
CellRight="cell_right"
CellColSpan="cell_colspan"
NowrapLeft="cell_left_nowrap"
NowrapCenter="cell_center_nowrap"
NowrapRight="cell_right_nowrap"
RowLeft="row_left"
RowCenter="row_center"
bDebug=False
}