Configure ERI servers in config plugins (#767)
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-05-18 16:26:51 +02:00
1 parent 378aaaa368
commit 7a09241888
43 files changed
+1165 -108

No files matched your search

@@ -0,0 +1,26 @@
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
<MudText Typo="Typo.body1" Class="mb-3">
@string.Format(T("How should AI Studio export the username and password configuration for the ERI v1 data source '{0}'?"), this.DataSource.Name)
</MudText>
<MudSelect @bind-Value="@this.usernamePasswordMode" Text="@this.GetUsernamePasswordModeText()" Label="@T("Username and password mode")" Class="mt-3 mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start">
@foreach (var mode in this.availableUsernamePasswordModes)
{
<MudSelectItem Value="@mode">
@this.GetUsernamePasswordModeText(mode)
</MudSelectItem>
}
</MudSelect>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel")
</MudButton>
<MudButton OnClick="@this.Export" Variant="Variant.Filled" Color="Color.Primary">
@T("Export")
</MudButton>
</DialogActions>
</MudDialog>
@@ -0,0 +1,37 @@
using AIStudio.Components;
using AIStudio.Settings.DataModel;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs;
public partial class DataSourceERIV1UsernamePasswordExportDialog : MSGComponentBase
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
[Parameter]
public DataSourceERI_V1 DataSource { get; set; }
private readonly DataSourceERIUsernamePasswordMode[] availableUsernamePasswordModes =
[
DataSourceERIUsernamePasswordMode.OS_USERNAME_SHARED_PASSWORD,
DataSourceERIUsernamePasswordMode.SHARED_USERNAME_AND_PASSWORD
];
private DataSourceERIUsernamePasswordMode usernamePasswordMode = DataSourceERIUsernamePasswordMode.OS_USERNAME_SHARED_PASSWORD;
private string GetUsernamePasswordModeText() => this.GetUsernamePasswordModeText(this.usernamePasswordMode);
private string GetUsernamePasswordModeText(DataSourceERIUsernamePasswordMode mode) => mode switch
{
DataSourceERIUsernamePasswordMode.OS_USERNAME_SHARED_PASSWORD => T("Read each user's username from the operating system and share one password"),
DataSourceERIUsernamePasswordMode.SHARED_USERNAME_AND_PASSWORD => T("Use the same username and password for all users"),
_ => T("User-managed username and password"),
};
private void Cancel() => this.MudDialog.Cancel();
private void Export() => this.MudDialog.Close(DialogResult.Ok(new DataSourceERIV1UsernamePasswordExportDialogResult(this.usernamePasswordMode)));
}
@@ -0,0 +1,5 @@
using AIStudio.Settings.DataModel;
namespace AIStudio.Dialogs;
public readonly record struct DataSourceERIV1UsernamePasswordExportDialogResult(DataSourceERIUsernamePasswordMode UsernamePasswordMode);
@@ -116,7 +116,7 @@ public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
if (this.dataAuthMethod is AuthMethod.TOKEN or AuthMethod.USERNAME_PASSWORD)
{
// Load the secret:
var requestedSecret = await this.RustService.GetSecret(this);
var requestedSecret = await this.RustService.GetSecret(this, SecretStoreType.DATA_SOURCE);
if (requestedSecret.Success)
this.dataSecret = await requestedSecret.Secret.Decrypt(this.encryption);
else
@@ -169,6 +169,7 @@ public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname,
AuthMethod = this.dataAuthMethod,
Username = this.dataUsername,
UsernamePasswordMode = DataSourceERIUsernamePasswordMode.USER_MANAGED,
Type = DataSourceType.ERI_V1,
SecurityPolicy = this.dataSecurityPolicy,
SelectedRetrievalId = this.dataSelectedRetrievalProcess.Id,
@@ -323,7 +324,7 @@ public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
if (!string.IsNullOrWhiteSpace(this.dataSecret))
{
// Store the secret in the OS secure storage:
var storeResponse = await this.RustService.SetSecret(this, this.dataSecret);
var storeResponse = await this.RustService.SetSecret(this, this.dataSecret, SecretStoreType.DATA_SOURCE);
if (!storeResponse.Success)
{
this.dataSecretStorageIssue = string.Format(T("Failed to store the auth. secret in the operating system. The message was: {0}. Please try again."), storeResponse.Issue);
@@ -21,7 +21,7 @@
@if (this.DataSource.AuthMethod is AuthMethod.USERNAME_PASSWORD)
{
<TextInfoLine Icon="@Icons.Material.Filled.Person2" Label="@T("Username")" Value="@this.DataSource.Username" ClipboardTooltipSubject="@T("the username")"/>
<TextInfoLine Icon="@Icons.Material.Filled.Person2" Label="@T("Username")" Value="@this.effectiveUsername" ClipboardTooltipSubject="@T("the username")"/>
}
<TextInfoLines Label="@T("Server description")" MaxLines="14" Value="@this.serverDescription" ClipboardTooltipSubject="@T("the server description")"/>
@@ -41,6 +41,7 @@ public partial class DataSourceERI_V1InfoDialog : MSGComponentBase, IAsyncDispos
private readonly List<string> dataIssues = [];
private string serverDescription = string.Empty;
private string effectiveUsername = string.Empty;
private ProviderType securityRequirements = ProviderType.NONE;
private IReadOnlyList<RetrievalInfo> retrievalInfoformation = [];
private RetrievalInfo selectedRetrievalInfo;
@@ -51,6 +52,27 @@ public partial class DataSourceERI_V1InfoDialog : MSGComponentBase, IAsyncDispos
private string Port => this.DataSource.Port == 0 ? string.Empty : $"{this.DataSource.Port}";
private async Task<(bool Success, DataSourceERI_V1 EffectiveDataSource)> CreateEffectiveDataSource()
{
this.effectiveUsername = this.DataSource.Username;
if (this.DataSource is not { AuthMethod: AuthMethod.USERNAME_PASSWORD, UsernamePasswordMode: DataSourceERIUsernamePasswordMode.OS_USERNAME_SHARED_PASSWORD })
return (true, this.DataSource);
var osUsername = await this.RustService.ReadUserName();
if (string.IsNullOrWhiteSpace(osUsername))
{
this.dataIssues.Add(T("Failed to read the user's username from the operating system."));
return (false, this.DataSource);
}
this.effectiveUsername = osUsername;
return (true, this.DataSource with
{
Username = osUsername,
UsernamePasswordMode = DataSourceERIUsernamePasswordMode.SHARED_USERNAME_AND_PASSWORD,
});
}
private string RetrievalName(RetrievalInfo retrievalInfo)
{
var hasId = !string.IsNullOrWhiteSpace(retrievalInfo.Id);
@@ -91,15 +113,19 @@ public partial class DataSourceERI_V1InfoDialog : MSGComponentBase, IAsyncDispos
{
this.IsOperationInProgress = true;
this.StateHasChanged();
var effectiveDataSourceResult = await this.CreateEffectiveDataSource();
if (!effectiveDataSourceResult.Success)
return;
using var client = ERIClientFactory.Get(ERIVersion.V1, this.DataSource);
using var client = ERIClientFactory.Get(ERIVersion.V1, effectiveDataSourceResult.EffectiveDataSource);
if(client is null)
{
this.dataIssues.Add(T("Failed to connect to the ERI v1 server. The server is not supported."));
return;
}
var loginResult = await client.AuthenticateAsync(this.RustService);
var loginResult = await client.AuthenticateAsync(this.RustService, cancellationToken: this.cts.Token);
if (!loginResult.Successful)
{
this.dataIssues.Add(loginResult.Message);
@@ -38,12 +38,27 @@
<MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap">
<MudIconButton Variant="Variant.Filled" Color="Color.Info" Icon="@Icons.Material.Filled.Info" OnClick="() => this.ShowInformation(context)"/>
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" OnClick="() => this.EditDataSource(context)">
@T("Edit")
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteDataSource(context)">
@T("Delete")
</MudButton>
@if (context.IsEnterpriseConfiguration)
{
<MudTooltip Text="@T("This data source is managed by your organization.")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Business" Disabled="true"/>
</MudTooltip>
}
else
{
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" OnClick="() => this.EditDataSource(context)">
@T("Edit")
</MudButton>
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings && context is DataSourceERI_V1)
{
<MudTooltip Text="@T("Export configuration")">
<MudIconButton Variant="Variant.Filled" Color="Color.Info" Icon="@Icons.Material.Filled.Dataset" OnClick="() => this.ExportDataSource(context)"/>
</MudTooltip>
}
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteDataSource(context)">
@T("Delete")
</MudButton>
}
</MudStack>
</MudTd>
</RowTemplate>
@@ -1,11 +1,17 @@
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.ERIClient.DataModel;
using AIStudio.Tools.PluginSystem;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs.Settings;
public partial class SettingsDialogDataSources : SettingsDialogBase
{
[Inject]
private ISnackbar Snackbar { get; init; } = null!;
private string GetEmbeddingName(IDataSource dataSource)
{
if(dataSource is IInternalDataSource internalDataSource)
@@ -86,9 +92,106 @@ public partial class SettingsDialogDataSources : SettingsDialogBase
await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
private async Task ExportDataSource(IDataSource dataSource)
{
if (!this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
return;
if (dataSource is not DataSourceERI_V1 eriDataSource)
return;
if (eriDataSource.AuthMethod is AuthMethod.KERBEROS)
{
await this.DialogService.ShowMessageBox(
T("Export ERI Data Source"),
T("Kerberos/SSO ERI data sources cannot be exported yet. Please configure them manually in the configuration plugin."),
T("Close"));
return;
}
var needsSecret = eriDataSource.AuthMethod is AuthMethod.TOKEN or AuthMethod.USERNAME_PASSWORD;
if (!needsSecret)
{
var publicLuaCode = eriDataSource.ExportAsConfigurationSection();
if (!string.IsNullOrWhiteSpace(publicLuaCode))
await this.RustService.CopyText2Clipboard(this.Snackbar, publicLuaCode);
return;
}
var secretResponse = await this.RustService.GetSecret(eriDataSource, SecretStoreType.DATA_SOURCE, isTrying: true);
if (!secretResponse.Success)
{
await this.DialogService.ShowMessageBox(
T("Export ERI Data Source"),
string.Format(T("Cannot export this ERI data source because no authentication secret is configured. The issue was: {0}"), secretResponse.Issue),
T("Close"));
return;
}
var encryption = PluginFactory.EnterpriseEncryption;
if (encryption?.IsAvailable != true)
{
await this.DialogService.ShowMessageBox(
T("Export ERI Data Source"),
T("Cannot export this ERI data source because no enterprise encryption secret is configured."),
T("Close"));
return;
}
var usernamePasswordMode = DataSourceERIUsernamePasswordMode.USER_MANAGED;
if (eriDataSource.AuthMethod is AuthMethod.TOKEN)
{
var dialogParameters = new DialogParameters<ConfirmDialog>
{
{ x => x.Message, T("This ERI data source has an access token configured. Do you want to include the encrypted access token in the export? Note: The recipient will need the same encryption secret to use the access token.") },
};
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Export Access Token?"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
}
else if (eriDataSource.AuthMethod is AuthMethod.USERNAME_PASSWORD)
{
var dialogParameters = new DialogParameters<DataSourceERIV1UsernamePasswordExportDialog>
{
{ x => x.DataSource, eriDataSource },
};
var dialogReference = await this.DialogService.ShowAsync<DataSourceERIV1UsernamePasswordExportDialog>(T("Export ERI Data Source"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is not DataSourceERIV1UsernamePasswordExportDialogResult exportResult)
return;
usernamePasswordMode = exportResult.UsernamePasswordMode;
}
var decryptedSecret = await secretResponse.Secret.Decrypt(Program.ENCRYPTION);
if (!encryption.TryEncrypt(decryptedSecret, out var encryptedSecret))
{
await this.DialogService.ShowMessageBox(
T("Export ERI Data Source"),
T("Cannot export this ERI data source because the authentication secret could not be encrypted."),
T("Close"));
return;
}
var luaCode = eriDataSource.ExportAsConfigurationSection(
encryptedSecret,
usernamePasswordMode);
if (string.IsNullOrWhiteSpace(luaCode))
return;
await this.RustService.CopyText2Clipboard(this.Snackbar, luaCode);
}
private async Task EditDataSource(IDataSource dataSource)
{
if (dataSource.IsEnterpriseConfiguration)
return;
IDataSource? editedDataSource = null;
switch (dataSource)
{
@@ -151,6 +254,9 @@ public partial class SettingsDialogDataSources : SettingsDialogBase
private async Task DeleteDataSource(IDataSource dataSource)
{
if (dataSource.IsEnterpriseConfiguration)
return;
var dialogParameters = new DialogParameters<ConfirmDialog>
{
{ x => x.Message, string.Format(T("Are you sure you want to delete the data source '{0}' of type {1}?"), dataSource.Name, dataSource.Type.GetDisplayName()) },
@@ -174,7 +280,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase
// All other auth methods require a secret, which we need to delete now:
else
{
var deleteSecretResponse = await this.RustService.DeleteSecret(externalDataSource);
var deleteSecretResponse = await this.RustService.DeleteSecret(externalDataSource, SecretStoreType.DATA_SOURCE);
if (deleteSecretResponse.Success)
applyChanges = true;
}