Added process Log Back and Import CSV

Still working on fixing the malformed csv issue but we're getting really close. Process Log Back will re-import all kills that were not recorded to the api. This was added so that any kills that were missed during downtime can be re-submitted to the api.
This commit is contained in:
Heavy Bob 2025-06-10 14:28:30 +10:00
parent 316911ba7d
commit 0f97d758f8
7 changed files with 413 additions and 227 deletions

View file

@ -52,115 +52,115 @@
Margin="0,0,0,10" Margin="0,0,0,10"
VerticalAlignment="Top"> VerticalAlignment="Top">
<StackPanel <StackPanel
VerticalAlignment="Top" VerticalAlignment="Top"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Width="152" Width="152"
Margin="10,5,10,5"> Margin="10,5,10,5">
<TextBlock Name="PilotNameTitle" <TextBlock Name="PilotNameTitle"
Text="Pilot" Text="Pilot"
Width="152" Width="152"
Height="20" Height="20"
Background="Transparent" Background="Transparent"
FontFamily="{StaticResource Orbitron}" FontFamily="{StaticResource Orbitron}"
Margin="0,5,0,0" Margin="0,5,0,0"
Foreground="{DynamicResource AltTextBrush}" Foreground="{DynamicResource AltTextBrush}"
FontSize="14"/> FontSize="14"/>
<TextBlock Name="PilotNameTextBox" <TextBlock Name="PilotNameTextBox"
Text="" Text=""
Width="152" Width="152"
Height="20" Height="20"
Background="Transparent" Background="Transparent"
FontFamily="{StaticResource Orbitron}" FontFamily="{StaticResource Orbitron}"
Margin="0,0,0,0" Margin="0,0,0,0"
Foreground="{DynamicResource TextBrush}" Foreground="{DynamicResource TextBrush}"
FontSize="10" FontSize="10"
TextAlignment="Center"/> TextAlignment="Center"/>
<TextBlock Name="PlayerShipTitle" <TextBlock Name="PlayerShipTitle"
Text="Ship" Text="Ship"
Width="152" Width="152"
Height="20" Height="20"
Background="Transparent" Background="Transparent"
FontFamily="{StaticResource Orbitron}" FontFamily="{StaticResource Orbitron}"
Margin="0,5,0,0" Margin="0,5,0,0"
Foreground="{DynamicResource AltTextBrush}" Foreground="{DynamicResource AltTextBrush}"
FontSize="14"/> FontSize="14"/>
<TextBlock Name="PlayerShipTextBox" <TextBlock Name="PlayerShipTextBox"
Text="" Text=""
Width="152" Width="152"
Height="20" Height="20"
Background="Transparent" Background="Transparent"
FontFamily="{StaticResource Orbitron}" FontFamily="{StaticResource Orbitron}"
Margin="0,0,0,0" Margin="0,0,0,0"
Foreground="{DynamicResource TextBrush}" Foreground="{DynamicResource TextBrush}"
FontSize="10" FontSize="10"
TextAlignment="Center"/> TextAlignment="Center"/>
<TextBlock Name="GameModeTitle" <TextBlock Name="GameModeTitle"
Text="Game Mode" Text="Game Mode"
Width="152" Width="152"
Height="20" Height="20"
Background="Transparent" Background="Transparent"
FontFamily="{StaticResource Orbitron}" FontFamily="{StaticResource Orbitron}"
Margin="0,5,0,0" Margin="0,5,0,0"
Foreground="{DynamicResource AltTextBrush}" Foreground="{DynamicResource AltTextBrush}"
FontSize="14"/> FontSize="14"/>
<TextBlock Name="GameModeTextBox" <TextBlock Name="GameModeTextBox"
Text="" Text=""
Width="152" Width="152"
Height="20" Height="20"
Background="Transparent" Background="Transparent"
FontFamily="{StaticResource Orbitron}" FontFamily="{StaticResource Orbitron}"
Margin="0,0,0,0" Margin="0,0,0,0"
Foreground="{DynamicResource TextBrush}" Foreground="{DynamicResource TextBrush}"
FontSize="10" FontSize="10"
TextAlignment="Center"/> TextAlignment="Center"/>
<TextBlock Name="LocationTitle" <TextBlock Name="LocationTitle"
Text="Location" Text="Location"
Width="152" Width="152"
Height="20" Height="20"
Background="Transparent" Background="Transparent"
FontFamily="{StaticResource Orbitron}" FontFamily="{StaticResource Orbitron}"
Margin="0,5,0,0" Margin="0,5,0,0"
Foreground="{DynamicResource AltTextBrush}" Foreground="{DynamicResource AltTextBrush}"
FontSize="14"/> FontSize="14"/>
<TextBlock Name="LocationTextBox" <TextBlock Name="LocationTextBox"
Text="Unknown" Text="Unknown"
Width="152" Width="152"
Height="20" Height="20"
Background="Transparent" Background="Transparent"
FontFamily="{StaticResource Orbitron}" FontFamily="{StaticResource Orbitron}"
Margin="0,0,0,0" Margin="0,0,0,0"
Foreground="{DynamicResource TextBrush}" Foreground="{DynamicResource TextBrush}"
FontSize="10" FontSize="10"
TextAlignment="Center"/> TextAlignment="Center"/>
<TextBlock Name="KillTallyTitle" <TextBlock Name="KillTallyTitle"
Text="Kill Tally" Text="Kill Tally"
Width="152" Width="152"
Height="20" Height="20"
Background="Transparent" Background="Transparent"
FontFamily="{StaticResource Orbitron}" FontFamily="{StaticResource Orbitron}"
Margin="0,5,0,0" Margin="0,5,0,0"
Foreground="{DynamicResource AltTextBrush}" Foreground="{DynamicResource AltTextBrush}"
FontSize="14"/> FontSize="14"/>
<TextBlock Name="KillTallyTextBox" <TextBlock Name="KillTallyTextBox"
Text="" Text=""
Width="152" Width="152"
Height="20" Height="20"
Background="Transparent" Background="Transparent"
FontFamily="{StaticResource Orbitron}" FontFamily="{StaticResource Orbitron}"
Margin="0,0,0,0" Margin="0,0,0,0"
Foreground="{DynamicResource TextBrush}" Foreground="{DynamicResource TextBrush}"
FontSize="10" FontSize="10"
TextAlignment="Center"/> TextAlignment="Center"/>
<TextBox x:Name="DebugPanel" <TextBox x:Name="DebugPanel"
Text="" Text=""
Width="152" Width="152"
Height="98" Height="98"
Background="Transparent" Background="Transparent"
FontFamily="{StaticResource Orbitron}" FontFamily="{StaticResource Orbitron}"
Foreground="{DynamicResource TextBrush}" Foreground="{DynamicResource TextBrush}"
FontSize="8" FontSize="8"
BorderThickness="0" BorderThickness="0"
Margin="0,9,0,0"/> Margin="0,9,0,0"/>
</StackPanel> </StackPanel>
</Border> </Border>
<StackPanel Grid.Row="1" <StackPanel Grid.Row="1"

View file

@ -21,14 +21,14 @@ public partial class HomePage : UserControl
private LogHandler? _logHandler; private LogHandler? _logHandler;
private KillHistoryManager _killHistoryManager; private KillHistoryManager _killHistoryManager;
private LogBackupProcessor? _logBackupProcessor;
private bool _UIEventsRegistered = false; private bool _UIEventsRegistered = false;
private System.Timers.Timer _statusCheckTimer; private System.Timers.Timer _statusCheckTimer;
private bool _isLogHandlerRunning = false; private bool _isLogHandlerRunning = false;
private int _counter = 1;
private System.Timers.Timer _counterTimer;
private bool _isInitializing = false; private bool _isInitializing = false;
private System.Timers.Timer? _initializationTimer; private System.Timers.Timer? _initializationTimer;
private bool _wasStarCitizenRunningOnStart = false; private bool _wasStarCitizenRunningOnStart = false;
private bool _isProcessingLogBackups = false;
public HomePage() public HomePage()
{ {
@ -62,15 +62,16 @@ public partial class HomePage : UserControl
_statusCheckTimer = new System.Timers.Timer(1000); // Check every second _statusCheckTimer = new System.Timers.Timer(1000); // Check every second
_statusCheckTimer.Elapsed += CheckStarCitizenStatus; _statusCheckTimer.Elapsed += CheckStarCitizenStatus;
_statusCheckTimer.Start(); _statusCheckTimer.Start();
// Initialize and start the counter timer
_counterTimer = new System.Timers.Timer(1000); // Update every second
_counterTimer.Elapsed += UpdateCounter;
_counterTimer.Start();
} }
private void CheckStarCitizenStatus(object? sender, ElapsedEventArgs e) private void CheckStarCitizenStatus(object? sender, ElapsedEventArgs e)
{ {
if (_isProcessingLogBackups)
{
// Simulate TrackR as running during log backup processing
Dispatcher.Invoke(() => UpdateStatusIndicator(true));
return;
}
bool isRunning = IsStarCitizenRunning(); bool isRunning = IsStarCitizenRunning();
Dispatcher.Invoke(() => Dispatcher.Invoke(() =>
{ {
@ -569,12 +570,40 @@ public partial class HomePage : UserControl
return Process.GetProcessesByName("StarCitizen").Length > 0; return Process.GetProcessesByName("StarCitizen").Length > 0;
} }
private void UpdateCounter(object? sender, ElapsedEventArgs e) public async void ProcessLogBackups_Click(object sender, RoutedEventArgs e)
{ {
Dispatcher.Invoke(() => if (_isProcessingLogBackups)
{ {
DebugPanel.Text = _counter.ToString(); MessageBox.Show("Already processing log backups. Please wait.", "Processing", MessageBoxButton.OK, MessageBoxImage.Information);
_counter = (_counter % 10) + 1; // Count from 1 to 10 and loop return;
}); }
try
{
_isProcessingLogBackups = true;
UpdateStatusIndicator(true, true); // Set to yellow for processing
if (_logBackupProcessor == null)
{
var logBackupsPath = Path.Combine(Path.GetDirectoryName(ConfigManager.LogFile)!, "logbackups");
_logBackupProcessor = new LogBackupProcessor(logBackupsPath, _killHistoryManager, _logHandler?.GetEventHandlers() ?? new List<ILogEventHandler>());
}
await _logBackupProcessor.ProcessLogBackupsAsync((logFile) =>
{
DebugPanel.Text = $"Processing: {Path.GetFileName(logFile)}";
});
MessageBox.Show("Log backups processed successfully!", "Success", MessageBoxButton.OK, MessageBoxImage.Information);
DebugPanel.Text = ""; // Clear the debug panel after successful processing
}
catch (Exception ex)
{
MessageBox.Show($"Error processing log backups: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
_isProcessingLogBackups = false;
UpdateStatusIndicator(IsStarCitizenRunning());
}
} }
} }

View file

@ -2,117 +2,132 @@
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Linq; using System.Linq;
using System.Diagnostics;
using System.Threading;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace AutoTrackR2; namespace AutoTrackR2;
public class KillHistoryManager public class KillHistoryManager
{ {
private string _killHistoryPath; private readonly string _killHistoryPath;
private readonly string _headers = "KillTime,EnemyPilot,EnemyShip,Enlisted,RecordNumber,OrgAffiliation,Player,Weapon,Ship,Method,Mode,GameVersion,TrackRver,Logged,PFP,Hash\n"; private readonly string _headers = "KillTime,EnemyPilot,EnemyShip,Enlisted,RecordNumber,OrgAffiliation,Player,Weapon,Ship,Method,Mode,GameVersion,TrackRver,Logged,PFP,Hash\n";
private readonly KillStreakManager _killStreakManager; private readonly KillStreakManager _killStreakManager;
private readonly ConcurrentQueue<KillData> _killQueue;
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly Task _processingTask;
private bool _killStreakSoundEnabled = true;
public KillHistoryManager(string logPath, string soundsPath) public KillHistoryManager(string logPath, string soundsPath)
{ {
_killHistoryPath = logPath; var appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "AutoTrackR2");
_killStreakManager = new KillStreakManager(soundsPath); Directory.CreateDirectory(appDataPath); // Ensure the directory exists
_killHistoryPath = Path.Combine(appDataPath, "Kill-log.csv");
// Create the CSV file with headers if it doesn't exist
if (!File.Exists(_killHistoryPath)) if (!File.Exists(_killHistoryPath))
{ {
File.WriteAllText(_killHistoryPath, _headers); File.WriteAllText(_killHistoryPath, _headers);
} }
else
_killStreakManager = new KillStreakManager(soundsPath);
_killQueue = new ConcurrentQueue<KillData>();
_cancellationTokenSource = new CancellationTokenSource();
// Start the background processing task
_processingTask = Task.Run(ProcessKillQueue);
}
private async Task ProcessKillQueue()
{
while (!_cancellationTokenSource.Token.IsCancellationRequested)
{ {
CheckAndFixMalformedCsv(); try
{
if (_killQueue.TryDequeue(out var kill))
{
await ProcessKillAsync(kill);
}
else
{
await Task.Delay(100, _cancellationTokenSource.Token);
}
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
Debug.WriteLine($"Error processing kill: {ex.Message}");
await Task.Delay(1000, _cancellationTokenSource.Token);
}
} }
} }
private void CheckAndFixMalformedCsv() private async Task ProcessKillAsync(KillData kill)
{ {
try try
{ {
// Try to read the file to check if it's malformed // Ensure all fields are properly escaped for CSV
using var reader = new StreamReader(_killHistoryPath); var fields = new[]
var firstLine = reader.ReadLine();
// If the file is empty or doesn't start with the correct headers, it's malformed
if (string.IsNullOrEmpty(firstLine) || firstLine != _headers.TrimEnd('\n'))
{ {
// Create a backup of the malformed file kill.KillTime.ToString(),
string backupPath = Path.Combine( EscapeCsvField(kill.EnemyPilot),
Path.GetDirectoryName(_killHistoryPath)!, EscapeCsvField(kill.EnemyShip),
"Kill-log.old" EscapeCsvField(kill.Enlisted),
); EscapeCsvField(kill.RecordNumber),
EscapeCsvField(kill.OrgAffiliation),
EscapeCsvField(kill.Player),
EscapeCsvField(kill.Weapon),
EscapeCsvField(kill.Ship),
EscapeCsvField(kill.Method),
EscapeCsvField(kill.Mode),
EscapeCsvField(kill.GameVersion),
EscapeCsvField(kill.TrackRver),
EscapeCsvField(kill.Logged),
EscapeCsvField(kill.PFP),
EscapeCsvField(kill.Hash)
};
// If Kill-log.old already exists, delete it var csvLine = string.Join(",", fields);
if (File.Exists(backupPath))
{
File.Delete(backupPath);
}
// Rename the malformed file // Use FileShare.Read to allow other processes to read while we write
File.Move(_killHistoryPath, backupPath); using var stream = new FileStream(_killHistoryPath, FileMode.Append, FileAccess.Write, FileShare.Read);
using var writer = new StreamWriter(stream);
// Create a new file with correct headers await writer.WriteLineAsync(csvLine);
File.WriteAllText(_killHistoryPath, _headers);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
// If there's any error reading the file, consider it malformed Debug.WriteLine($"Error writing kill to CSV: {ex.Message}");
Console.WriteLine($"Error reading CSV file: {ex.Message}"); throw;
string backupPath = Path.Combine(
Path.GetDirectoryName(_killHistoryPath)!,
"Kill-log.old"
);
// If Kill-log.old already exists, delete it
if (File.Exists(backupPath))
{
File.Delete(backupPath);
}
// Rename the malformed file
File.Move(_killHistoryPath, backupPath);
// Create a new file with correct headers
File.WriteAllText(_killHistoryPath, _headers);
} }
} }
public void AddKill(KillData killData) private string EscapeCsvField(string field)
{ {
// Ensure the CSV file exists if (string.IsNullOrEmpty(field)) return "";
// This should only happen if the file was deleted or corrupted
if (!File.Exists(_killHistoryPath))
{
File.WriteAllText(_killHistoryPath, _headers);
}
// Remove comma from Enlisted
killData.Enlisted = killData.Enlisted?.Replace(",", string.Empty);
// Append the new kill data to the CSV file
var csv = new StringBuilder();
csv.AppendLine($"\"{killData.KillTime}\",\"{killData.EnemyPilot}\",\"{killData.EnemyShip}\",\"{killData.Enlisted}\",\"{killData.RecordNumber}\",\"{killData.OrgAffiliation}\",\"{killData.Player}\",\"{killData.Weapon}\",\"{killData.Ship}\",\"{killData.Method}\",\"{killData.Mode}\",\"{killData.GameVersion}\",\"{killData.TrackRver}\",\"{killData.Logged}\",\"{killData.PFP}\",\"{killData.Hash}\"");
// Check file can be written to // If the field contains any special characters, wrap it in quotes
try if (field.Contains(",") || field.Contains("\"") || field.Contains("\n") || field.Contains("\r"))
{ {
using var fileStream = new FileStream(_killHistoryPath, FileMode.Append, FileAccess.Write, FileShare.Read); // Double up any quotes
using var writer = new StreamWriter(fileStream); field = field.Replace("\"", "\"\"");
writer.Write(csv.ToString()); return $"\"{field}\"";
}
// Trigger kill streak sound only if enabled return field;
if (ConfigManager.KillStreakEnabled == 1) }
{
_killStreakManager.OnKill(); public void AddKill(KillData kill)
} {
} _killQueue.Enqueue(kill);
catch (IOException ex) }
{
// Handle the exception (e.g., log it) public void PlayKillStreakSound()
Console.WriteLine($"Error writing to file: {ex.Message}"); {
} _killStreakManager.OnKill();
} }
public void ResetKillStreak() public void ResetKillStreak()
@ -120,23 +135,30 @@ public class KillHistoryManager
_killStreakManager.OnDeath(); _killStreakManager.OnDeath();
} }
public void Dispose()
{
_cancellationTokenSource.Cancel();
_processingTask.Wait();
_cancellationTokenSource.Dispose();
}
public List<KillData> GetKills() public List<KillData> GetKills()
{ {
var kills = new List<KillData>(); var kills = new List<KillData>();
using var reader = new StreamReader(new FileStream(_killHistoryPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); using var reader = new StreamReader(new FileStream(_killHistoryPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
reader.ReadLine(); // Skip headers reader.ReadLine(); // Skip headers
while (reader.Peek() >= 0) while (reader.Peek() >= 0)
{ {
var line = reader.ReadLine(); var line = reader.ReadLine();
// Remove extra quotes from CSV data // Remove extra quotes from CSV data
// Todo: These quotes are for handling commas in the data, but not sure if they're necessary // Todo: These quotes are for handling commas in the data, but not sure if they're necessary
line = line?.Replace("\"", string.Empty); line = line?.Replace("\"", string.Empty);
var data = line?.Split(','); var data = line?.Split(',');
kills.Add(new KillData kills.Add(new KillData
{ {
KillTime = data?[0], KillTime = data?[0],

View file

@ -0,0 +1,83 @@
using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using AutoTrackR2.LogEventHandlers;
using System.Windows;
using System.Linq;
using System.Threading;
namespace AutoTrackR2;
public class LogBackupProcessor
{
private readonly string _logBackupsPath;
private readonly KillHistoryManager _killHistoryManager;
private readonly List<ILogEventHandler> _logEventHandlers;
public LogBackupProcessor(string logBackupsPath, KillHistoryManager killHistoryManager, List<ILogEventHandler> logEventHandlers)
{
_logBackupsPath = logBackupsPath;
_killHistoryManager = killHistoryManager;
_logEventHandlers = logEventHandlers;
}
public async Task ProcessLogBackupsAsync(Action<string>? onLogFileProcessed = null)
{
if (!Directory.Exists(_logBackupsPath))
{
Console.WriteLine($"Log backups directory not found: {_logBackupsPath}");
return;
}
var logFiles = Directory.GetFiles(_logBackupsPath, "*.log", SearchOption.AllDirectories)
.Where(file => File.GetLastWriteTime(file) >= new DateTime(2025, 3, 27))
.ToArray();
Array.Sort(logFiles); // Process files in chronological order
for (int i = 0; i < logFiles.Length; i++)
{
var logFile = logFiles[i];
onLogFileProcessed?.Invoke(logFile);
await ProcessLogFileAsync(logFile);
}
}
private async Task ProcessLogFileAsync(string logFilePath)
{
try
{
using var reader = new StreamReader(logFilePath);
string? line;
var lines = new List<string>();
while ((line = await reader.ReadLineAsync()) != null)
{
lines.Add(line);
}
int actorDeathCount = 0;
await Task.Run(() => Parallel.ForEach(lines, line =>
{
var entry = new LogEntry { Message = line };
foreach (var handler in _logEventHandlers)
{
if (handler.Pattern.IsMatch(line))
{
handler.Handle(entry);
if (handler is ActorDeathEvent)
{
Interlocked.Increment(ref actorDeathCount);
}
}
}
}));
Console.WriteLine($"Processed {actorDeathCount} actor deaths in {logFilePath}");
// Wait 5 seconds after processing the file before moving to the next file
await Task.Delay(TimeSpan.FromSeconds(5));
}
catch (Exception ex)
{
Console.WriteLine($"Error processing log file {logFilePath}: {ex.Message}");
}
}
}

View file

@ -46,9 +46,10 @@ public class LogHandler
new GameVersionEvent(), new GameVersionEvent(),
new JumpDriveStateChangedEvent(), new JumpDriveStateChangedEvent(),
new RequestJumpFailedEvent(), new RequestJumpFailedEvent(),
new VehicleDestructionEvent() new VehicleDestructionEvent(),
new ActorDeathEvent()
]; ];
public LogHandler(string? logPath) public LogHandler(string? logPath)
{ {
if (string.IsNullOrEmpty(logPath)) if (string.IsNullOrEmpty(logPath))
@ -104,8 +105,6 @@ public class LogHandler
HandleLogEntry(line); HandleLogEntry(line);
} }
// Ensures that any deaths already in log aren't sent to the APIs until the monitor thread is running
_eventHandlers.Add(new ActorDeathEvent());
StartMonitoring(); StartMonitoring();
} }
@ -218,4 +217,9 @@ public class LogHandler
} }
_gameProcessState = newGameProcessState; _gameProcessState = newGameProcessState;
} }
public List<ILogEventHandler> GetEventHandlers()
{
return new List<ILogEventHandler>(_eventHandlers);
}
} }

View file

@ -1,37 +1,80 @@
<Window x:Class="AutoTrackR2.MainWindow" <Window x:Class="AutoTrackR2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="AutoTrackR" Height="450" Width="800" Title="AutoTrackR"
WindowStyle="None" ResizeMode="NoResize" Height="450"
Width="800"
WindowStyle="None"
ResizeMode="NoResize"
AllowsTransparency="True" AllowsTransparency="True"
Style="{StaticResource CustomWindowStyle}"> Style="{StaticResource CustomWindowStyle}">
<Grid> <Grid>
<!-- Custom Title Bar --> <!-- Custom Title Bar -->
<DockPanel Height="30" VerticalAlignment="Top" MouseDown="TitleBar_MouseDown" Margin="5" Background="Transparent"> <DockPanel Height="30"
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right"> VerticalAlignment="Top"
<Button Content="_" Width="30" Height="25" Click="MinimizeWindow" Style="{StaticResource TitleButtonStyle}" FontFamily="{StaticResource Orbitron}"/> MouseDown="TitleBar_MouseDown"
<Button Content="X" Width="30" Height="25" Click="CloseWindow" Style="{StaticResource TitleButtonStyle}" FontFamily="{StaticResource Orbitron}"/> Margin="5"
Background="Transparent">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="_"
Width="30"
Height="25"
Click="MinimizeWindow"
Style="{StaticResource TitleButtonStyle}"
FontFamily="{StaticResource Orbitron}"/>
<Button Content="X"
Width="30"
Height="25"
Click="CloseWindow"
Style="{StaticResource TitleButtonStyle}"
FontFamily="{StaticResource Orbitron}"/>
</StackPanel> </StackPanel>
</DockPanel> </DockPanel>
<!-- Main Content Area --> <!-- Main Content Area -->
<Grid Margin="0,30,0,0"> <Grid Margin="0,30,0,0">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/> <ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- Left Tab Panel --> <!-- Left Tab Panel -->
<StackPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="5,0,0,0"> <StackPanel VerticalAlignment="Stretch"
<Image x:Name="Logo" Height="138" Source="/Assets/AutoTrackR.png" Stretch="Fill" Width="141" RenderOptions.BitmapScalingMode="Fant"/> HorizontalAlignment="Stretch"
<Button Content="Home" Name="HomeTab" Margin="10,40,10,10" Height="40" Style="{StaticResource TabButtonStyle}" Click="TabButton_Click"/> Margin="5,0,0,0">
<Button Content="Config" Name="ConfigTab" Margin="10" Height="40" Style="{StaticResource TabButtonStyle}" Click="TabButton_Click"/> <Image x:Name="Logo"
</StackPanel> Height="138"
Source="/Assets/AutoTrackR.png"
Stretch="Fill"
Width="141"
RenderOptions.BitmapScalingMode="Fant"/>
<Button Content="Home"
Name="HomeTab"
Margin="10,40,10,10"
Height="40"
Style="{StaticResource TabButtonStyle}"
Click="TabButton_Click"/>
<Button Content="Config"
Name="ConfigTab"
Margin="10"
Height="40"
Style="{StaticResource TabButtonStyle}"
Click="TabButton_Click"/>
<Button Content="Process Log Backups"
Name="ProcessLogBackupsButton"
Margin="10"
Height="40"
Style="{StaticResource TabButtonStyle}"
Click="ProcessLogBackups_Click"/>
</StackPanel>
<!-- Content Area --> <!-- Content Area -->
<ContentControl Grid.Column="1" Name="ContentControl" Margin="10"> <ContentControl Grid.Column="1"
<!-- Default content can be set here --> Name="ContentControl"
</ContentControl> Margin="10">
</Grid> <!-- Default content can be set here -->
</ContentControl>
</Grid>
</Grid> </Grid>
</Window> </Window>

View file

@ -120,6 +120,11 @@ namespace AutoTrackR2
UpdateTabVisuals(); UpdateTabVisuals();
} }
private void ProcessLogBackups_Click(object sender, RoutedEventArgs e)
{
homePage.ProcessLogBackups_Click(sender, e);
}
private void UpdateTabStates(string activeTab) private void UpdateTabStates(string activeTab)
{ {
foreach (var key in tabStates.Keys) foreach (var key in tabStates.Keys)