Skip to content

Commit 10f603a

Browse files
authored
Merge pull request #5 from SwaggyMacro/copilot/add-multiple-server-support
Add concurrent multi-server IPMI management and targeting
2 parents 28aa51f + 388ec10 commit 10f603a

21 files changed

+936
-123
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@
22
bin/
33
obj/
44
.idea/
5-
*.db
5+
*.db
6+
*.db-shm
7+
*.db-wal
8+
*.db;*

FanX/Components/Dialogs/AddEditConditionDialog.razor

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@using FanX.Resources
22
@inject LocalizationService LocalizationService
33
@inject DatabaseService DbService
4+
@inject IpmiConfigService IpmiConfigService
45
@implements IDisposable
56

67
<MudDialog>
@@ -62,8 +63,13 @@
6263

6364
private async Task LoadSensorNamesAsync()
6465
{
65-
_sensorNames = await DbService.Db.Queryable<SensorData>()
66-
.Select(s => s.SensorName)
66+
var activeConfig = await IpmiConfigService.GetConfigAsync();
67+
var query = DbService.Db.Queryable<SensorData>();
68+
if (activeConfig.Id != 0)
69+
{
70+
query = query.Where(s => s.IpmiConfigId == activeConfig.Id);
71+
}
72+
_sensorNames = await query.Select(s => s.SensorName)
6773
.Distinct()
6874
.ToListAsync();
6975
StateHasChanged();

FanX/Components/Dialogs/AddEditFanRuleDialog.razor

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
@using FanX.Resources
2+
@using FanX.Models
23
@inject FanControlService FanControlService
34
@inject ISnackbar Snackbar
45
@inject IDialogService DialogService
56
@inject LocalizationService LocalizationService
67
@inject SensorDataService SensorDataService
8+
@inject IpmiConfigService IpmiConfigService
79
@implements IDisposable
810

911
<MudDialog>
@@ -17,6 +19,23 @@
1719
<MudForm @ref="_form" @bind-IsValid="@_success" Class="mb-4">
1820
<MudTextField @bind-Value="@_rule.Name" Label="@Localization.RuleName" Required="true" />
1921

22+
<MudSelect T="int" Label="@LocalizationService.GetLocalizedString("TargetServers")" MultiSelection="true"
23+
SelectedValues="_rule.TargetIpmiConfigIds"
24+
SelectedValuesChanged="(values) => { if (values != null) _rule.TargetIpmiConfigIds = values.ToList(); }"
25+
HelperText="@LocalizationService.GetLocalizedString("TargetServersHelpText")">
26+
@if (!_availableServers.Any())
27+
{
28+
<MudSelectItem T="int" Value="@(-1)" Disabled="true">@Localization.Loading</MudSelectItem>
29+
}
30+
else
31+
{
32+
@foreach (var server in _availableServers)
33+
{
34+
<MudSelectItem T="int" Value="@server.Id">@GetServerLabel(server)</MudSelectItem>
35+
}
36+
}
37+
</MudSelect>
38+
2039
<MudSelect T="string" Label="@Localization.TargetFans" MultiSelection="true"
2140
SelectedValues="_rule.TargetFanNames"
2241
SelectedValuesChanged="(values) => { if (values != null) _rule.TargetFanNames = values.ToList(); }"
@@ -97,9 +116,13 @@
97116
[Parameter]
98117
public List<SensorData> AvailableSensors { get; set; } = new();
99118

119+
[Parameter]
120+
public List<FanX.Models.IpmiConfig> AvailableServers { get; set; } = new();
121+
100122
private FanControlRule? _rule;
101123
private MudForm? _form;
102124
private bool _success;
125+
private List<FanX.Models.IpmiConfig> _availableServers = new();
103126

104127
protected override async Task OnInitializedAsync()
105128
{
@@ -119,6 +142,12 @@
119142
{
120143
_localSensors = await SensorDataService.GetSensorsAsync();
121144
}
145+
146+
_availableServers = AvailableServers ?? new List<FanX.Models.IpmiConfig>();
147+
if (!_availableServers.Any())
148+
{
149+
_availableServers = await IpmiConfigService.GetConfigsAsync();
150+
}
122151
}
123152

124153
private void Cancel() => MudDialog.Cancel();
@@ -209,7 +238,23 @@
209238
{
210239
LocalizationService.OnLanguageChanged -= StateHasChanged;
211240
}
212-
}
241+
242+
private string GetServerLabel(FanX.Models.IpmiConfig config)
243+
{
244+
if (!string.IsNullOrWhiteSpace(config.Name))
245+
{
246+
return config.IsEnabled ? config.Name : $"{config.Name} ({LocalizationService.GetLocalizedString("Disabled")})";
247+
}
248+
249+
if (!string.IsNullOrWhiteSpace(config.Host))
250+
{
251+
return config.IsEnabled ? config.Host : $"{config.Host} ({LocalizationService.GetLocalizedString("Disabled")})";
252+
}
253+
254+
var label = $"{LocalizationService.GetLocalizedString("Server")} {config.Id}";
255+
return config.IsEnabled ? label : $"{label} ({LocalizationService.GetLocalizedString("Disabled")})";
256+
}
257+
}
213258
<style>
214259
.form-check.form-switch .form-check-input {
215260
width: 3.0rem;
@@ -234,4 +279,4 @@
234279
position: relative;
235280
top: 0.15rem;
236281
}
237-
</style>
282+
</style>
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
@using FanX.Resources
2+
@inject IpmiConfigService IpmiConfigService
3+
@inject ISnackbar Snackbar
4+
@inject LocalizationService LocalizationService
5+
@inject IDialogService DialogService
6+
@implements IDisposable
7+
8+
<MudDialog>
9+
<DialogContent>
10+
<MudForm @ref="_form" @bind-IsValid="@_success">
11+
<MudTextField Label="@LocalizationService.GetLocalizedString("ServerName")" @bind-Value="_config.Name" />
12+
<MudTextField Label="@Localization.HostnameOrIpAddress" @bind-Value="_config.Host" Required="true" />
13+
<MudTextField Label="@Localization.Username" @bind-Value="_config.Username" Required="true" />
14+
<MudTextField Label="@Localization.Password" @bind-Value="_config.Password" InputType="InputType.Password" Required="true" />
15+
16+
<div class="form-check form-switch mt-4">
17+
<input class="form-check-input" type="checkbox" role="switch"
18+
checked="@_config.IsEnabled"
19+
@onchange="e => { if (e.Value is bool val) { _config.IsEnabled = val; StateHasChanged(); } }" />
20+
<label class="form-check-label ms-2">@Localization.Enabled</label>
21+
</div>
22+
</MudForm>
23+
</DialogContent>
24+
<DialogActions>
25+
@if (!_isNew)
26+
{
27+
<MudButton Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" OnClick="@DeleteConfig">@Localization.Delete</MudButton>
28+
}
29+
<MudSpacer />
30+
<MudButton OnClick="@Cancel">@Localization.Cancel</MudButton>
31+
<MudButton Color="Color.Primary" StartIcon="@Icons.Material.Filled.Save" OnClick="@SaveConfig">@Localization.SaveConfiguration</MudButton>
32+
</DialogActions>
33+
</MudDialog>
34+
35+
@code {
36+
[CascadingParameter]
37+
IMudDialogInstance MudDialog { get; set; } = default!;
38+
39+
[Parameter]
40+
public int ConfigId { get; set; }
41+
42+
private FanX.Models.IpmiConfig _config = new();
43+
private MudForm _form = default!;
44+
private bool _success;
45+
private bool _isNew;
46+
47+
protected override async Task OnInitializedAsync()
48+
{
49+
LocalizationService.OnLanguageChanged += StateHasChanged;
50+
if (ConfigId != 0)
51+
{
52+
_config = await IpmiConfigService.GetConfigByIdAsync(ConfigId);
53+
if (_config.Id == 0)
54+
{
55+
Snackbar.Add(LocalizationService.GetLocalizedString("ServerNotFound"), Severity.Error);
56+
MudDialog.Cancel();
57+
return;
58+
}
59+
}
60+
61+
_isNew = _config.Id == 0;
62+
if (_isNew)
63+
{
64+
_config.IsEnabled = true;
65+
}
66+
}
67+
68+
private void Cancel() => MudDialog.Cancel();
69+
70+
private async Task SaveConfig()
71+
{
72+
await _form.Validate();
73+
if (!_success)
74+
{
75+
Snackbar.Add(Localization.FixValidationErrors, Severity.Warning);
76+
return;
77+
}
78+
79+
_config = await IpmiConfigService.SaveConfigAsync(_config);
80+
MudDialog.Close(DialogResult.Ok(new IpmiConfigDialogResult { Config = _config }));
81+
}
82+
83+
private async Task DeleteConfig()
84+
{
85+
var result = await DialogService.ShowMessageBox(
86+
Localization.ConfirmDeletion,
87+
LocalizationService.GetLocalizedString("ConfirmDeleteServerMessage"),
88+
yesText: Localization.Delete,
89+
cancelText: Localization.Cancel);
90+
91+
if (result == true)
92+
{
93+
var deleted = await IpmiConfigService.DeleteConfigAsync(_config.Id);
94+
if (!deleted)
95+
{
96+
Snackbar.Add(LocalizationService.GetLocalizedString("ServerDeleteFailed"), Severity.Error);
97+
return;
98+
}
99+
MudDialog.Close(DialogResult.Ok(new IpmiConfigDialogResult { ConfigId = _config.Id, Deleted = true }));
100+
}
101+
}
102+
103+
public void Dispose()
104+
{
105+
LocalizationService.OnLanguageChanged -= StateHasChanged;
106+
}
107+
108+
public class IpmiConfigDialogResult
109+
{
110+
public FanX.Models.IpmiConfig? Config { get; set; }
111+
public int ConfigId { get; set; }
112+
public bool Deleted { get; set; }
113+
}
114+
}

FanX/Components/Pages/Dashboard.razor

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -404,9 +404,16 @@
404404
{
405405
await using var scope = ScopeFactory.CreateAsyncScope();
406406
var db = scope.ServiceProvider.GetRequiredService<DatabaseService>().Db;
407+
var configService = scope.ServiceProvider.GetRequiredService<IpmiConfigService>();
408+
var activeConfig = await configService.GetConfigAsync();
409+
var activeConfigId = activeConfig.Id;
407410

408-
var latestTimestamp = await db.Queryable<SensorData>()
409-
.MaxAsync(s => s.Timestamp);
411+
var latestTimestampQuery = db.Queryable<SensorData>();
412+
if (activeConfigId != 0)
413+
{
414+
latestTimestampQuery = latestTimestampQuery.Where(s => s.IpmiConfigId == activeConfigId);
415+
}
416+
var latestTimestamp = await latestTimestampQuery.MaxAsync(s => s.Timestamp);
410417

411418
if (latestTimestamp == default)
412419
{
@@ -423,9 +430,12 @@
423430
return;
424431
}
425432

426-
var latestData = await db.Queryable<SensorData>()
427-
.Where(s => s.Timestamp == latestTimestamp)
428-
.ToListAsync();
433+
var latestDataQuery = db.Queryable<SensorData>().Where(s => s.Timestamp == latestTimestamp);
434+
if (activeConfigId != 0)
435+
{
436+
latestDataQuery = latestDataQuery.Where(s => s.IpmiConfigId == activeConfigId);
437+
}
438+
var latestData = await latestDataQuery.ToListAsync();
429439

430440
// Only update if we got actual data
431441
if (latestData != null && latestData.Any())
@@ -483,6 +493,9 @@
483493
{
484494
await using var scope = ScopeFactory.CreateAsyncScope();
485495
var db = scope.ServiceProvider.GetRequiredService<DatabaseService>().Db;
496+
var configService = scope.ServiceProvider.GetRequiredService<IpmiConfigService>();
497+
var activeConfig = await configService.GetConfigAsync();
498+
var activeConfigId = activeConfig.Id;
486499

487500
var now = DateTime.Now;
488501
var hourAgo = now.AddHours(-1);
@@ -491,12 +504,17 @@
491504
var monthAgo = now.AddDays(-30);
492505

493506
// Load all sensor data from the past month in one query
494-
var allData = await db.Queryable<SensorData>()
507+
var allDataQuery = db.Queryable<SensorData>()
495508
.Where(s => s.Timestamp >= monthAgo &&
496509
s.SensorName != null &&
497510
(s.SensorType == "Temperature" ||
498511
s.SensorType == "Fan" ||
499-
s.SensorName == "Pwr Consumption"))
512+
s.SensorName == "Pwr Consumption"));
513+
if (activeConfigId != 0)
514+
{
515+
allDataQuery = allDataQuery.Where(s => s.IpmiConfigId == activeConfigId);
516+
}
517+
var allData = await allDataQuery
500518
.Select(s => new { s.SensorName, s.SensorType, s.Reading, s.Timestamp })
501519
.ToListAsync();
502520

@@ -558,14 +576,22 @@
558576

559577
await using var scope = ScopeFactory.CreateAsyncScope();
560578
var db = scope.ServiceProvider.GetRequiredService<DatabaseService>().Db;
579+
var configService = scope.ServiceProvider.GetRequiredService<IpmiConfigService>();
580+
var activeConfig = await configService.GetConfigAsync();
581+
var activeConfigId = activeConfig.Id;
561582

562583
var now = DateTime.Now;
563584
var startTime = now.AddHours(-_selectedTimeRange);
564585
var totalMinutes = _selectedTimeRange * 60;
565586
var intervalMinutes = Math.Max(1, totalMinutes / MaxChartPoints);
566587

567-
var rawData = await db.Queryable<SensorData>()
568-
.Where(s => s.Timestamp >= startTime)
588+
var rawDataQuery = db.Queryable<SensorData>()
589+
.Where(s => s.Timestamp >= startTime);
590+
if (activeConfigId != 0)
591+
{
592+
rawDataQuery = rawDataQuery.Where(s => s.IpmiConfigId == activeConfigId);
593+
}
594+
var rawData = await rawDataQuery
569595
.GroupBy(s => new {
570596
s.SensorName,
571597
s.SensorType,

0 commit comments

Comments
 (0)