Added organization-wide management for data backup and restore

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
j-erler 2026-09-26 01:09:54 +02:00
parent a8e8edc028
commit a2afd69af2
6 changed files with 63 additions and 9 deletions

View File

@ -81,9 +81,7 @@
} }
else else
{ {
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Save" OnClick="@this.StartExportAsync" Disabled="@(this.isBusy || !this.IsAnythingSelected)"> <LockableButton Text="@string.Format(T("Export {0} chats"), this.SelectedChatCount)" Icon="@Icons.Material.Filled.Save" OnClickAsync="@this.StartExportAsync" Disabled="@(() => this.isBusy || !this.IsAnythingSelected)" IsLocked="@(() => !this.AllowChatExport)" NoMargin="@true"/>
@string.Format(T("Export {0} chats"), this.SelectedChatCount)
</MudButton>
} }
} }
@ -133,9 +131,7 @@
</MudText> </MudText>
<MudStack Row="@true" AlignItems="@AlignItems.Center" Spacing="2" Class="mb-3"> <MudStack Row="@true" AlignItems="@AlignItems.Center" Spacing="2" Class="mb-3">
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.FolderOpen" OnClick="@this.ChooseArchiveAsync" Disabled="@this.isBusy"> <LockableButton Text="@T("Choose archive")" Icon="@Icons.Material.Filled.FolderOpen" OnClickAsync="@this.ChooseArchiveAsync" Disabled="@(() => this.isBusy)" IsLocked="@(() => !this.AllowChatImport)" NoMargin="@true"/>
@T("Choose archive")
</MudButton>
@if (!string.IsNullOrWhiteSpace(this.selectedArchivePath)) @if (!string.IsNullOrWhiteSpace(this.selectedArchivePath))
{ {
<MudText Typo="Typo.body2"> <MudText Typo="Typo.body2">

View File

@ -53,7 +53,18 @@ public partial class SettingsPanelDataBackup : SettingsPanelBase
private int SelectedChatCount => this.workspaceSelections.Where(selection => selection.IsSelected).Sum(selection => selection.ChatCount) + (this.includeTemporaryChats ? this.temporaryChatCount : 0); private int SelectedChatCount => this.workspaceSelections.Where(selection => selection.IsSelected).Sum(selection => selection.ChatCount) + (this.includeTemporaryChats ? this.temporaryChatCount : 0);
private bool CanImport => this.importPreview is { Success: true, Manifest.TotalChatCount: > 0 }; private bool AllowChatExport => this.SettingsManager.ConfigurationData.App.AllowUserToExportChats;
private bool AllowChatImport => this.SettingsManager.ConfigurationData.App.AllowUserToImportChats;
/// <summary>
/// Whether the chosen archive can be imported now.
/// </summary>
/// <remarks>
/// The permission is asked here again and not only when choosing the archive: a
/// configuration plugin may take it away while a chosen archive waits to be imported.
/// </remarks>
private bool CanImport => this.importPreview is { Success: true, Manifest.TotalChatCount: > 0 } && this.AllowChatImport;
private double ProgressPercentage => this.progress.TotalChats is 0 ? 0d : this.progress.ProcessedChats * 100d / this.progress.TotalChats; private double ProgressPercentage => this.progress.TotalChats is 0 ? 0d : this.progress.ProcessedChats * 100d / this.progress.TotalChats;
@ -159,7 +170,7 @@ public partial class SettingsPanelDataBackup : SettingsPanelBase
private async Task StartExportAsync() private async Task StartExportAsync()
{ {
if (this.isBusy || !this.IsAnythingSelected) if (this.isBusy || !this.IsAnythingSelected || !this.AllowChatExport)
return; return;
this.isBusy = true; this.isBusy = true;
@ -184,6 +195,9 @@ public partial class SettingsPanelDataBackup : SettingsPanelBase
this.progress = new(0, this.SelectedChatCount); this.progress = new(0, this.SelectedChatCount);
this.cancellationTokenSource = new(); this.cancellationTokenSource = new();
// Show the progress and the cancel button before the first chat is written:
await this.InvokeAsync(this.StateHasChanged);
// Compressing on the UI thread would keep the progress bar and the cancel // Compressing on the UI thread would keep the progress bar and the cancel
// button from updating for the whole run: // button from updating for the whole run:
var token = this.cancellationTokenSource.Token; var token = this.cancellationTokenSource.Token;
@ -199,6 +213,10 @@ public partial class SettingsPanelDataBackup : SettingsPanelBase
this.isProcessing = false; this.isProcessing = false;
this.DisposeCancellation(); this.DisposeCancellation();
this.isBusy = false; this.isBusy = false;
// The lockable button calls this as a plain function, not as an event of this
// component, so nothing renders the result unless we do:
await this.InvokeAsync(this.StateHasChanged);
} }
} }
@ -243,7 +261,7 @@ public partial class SettingsPanelDataBackup : SettingsPanelBase
private async Task ChooseArchiveAsync() private async Task ChooseArchiveAsync()
{ {
if (this.isBusy) if (this.isBusy || !this.AllowChatImport)
return; return;
this.isBusy = true; this.isBusy = true;
@ -260,6 +278,10 @@ public partial class SettingsPanelDataBackup : SettingsPanelBase
finally finally
{ {
this.isBusy = false; this.isBusy = false;
// The lockable button calls this as a plain function, not as an event of this
// component, so nothing renders the preview unless we do:
await this.InvokeAsync(this.StateHasChanged);
} }
} }

View File

@ -411,6 +411,17 @@ CONFIG["SETTINGS"] = {}
-- On Linux, this button exports the plugin archive instead of using a native share sheet. -- On Linux, this button exports the plugin archive instead of using a native share sheet.
-- CONFIG["SETTINGS"]["DataApp.AllowUserToSharePlugins"] = false -- CONFIG["SETTINGS"]["DataApp.AllowUserToSharePlugins"] = false
-- Configure the user permission to export chats into an archive file, which the data backup
-- in the settings writes. When set to false, the export button stays visible but is disabled.
-- Chats often hold confidential content, and an archive is a single file which can leave the
-- computer on any stick or cloud drive.
-- CONFIG["SETTINGS"]["DataApp.AllowUserToExportChats"] = false
-- Configure the user permission to import chats from an archive file. When set to false, the
-- button to choose an archive stays visible but is disabled. An archive brings along the
-- locations of the documents its chats name, and those are read again when a chat continues.
-- CONFIG["SETTINGS"]["DataApp.AllowUserToImportChats"] = false
-- Configure whether administration settings are visible in the UI: -- Configure whether administration settings are visible in the UI:
-- CONFIG["SETTINGS"]["DataApp.ShowAdminSettings"] = true -- CONFIG["SETTINGS"]["DataApp.ShowAdminSettings"] = true

View File

@ -201,6 +201,24 @@ public sealed class DataApp(Expression<Func<Data, DataApp>>? configSelection = n
/// </summary> /// </summary>
public bool AllowUserToSharePlugins { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToSharePlugins, true); public bool AllowUserToSharePlugins { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToSharePlugins, true);
/// <summary>
/// Should the user be allowed to export chats into an archive file?
/// </summary>
/// <remarks>
/// Chats often hold confidential content, and an archive is a single file which can leave
/// the computer on any stick or cloud drive.
/// </remarks>
public bool AllowUserToExportChats { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToExportChats, true);
/// <summary>
/// Should the user be allowed to import chats from an archive file?
/// </summary>
/// <remarks>
/// An archive brings along everything its chats name, including the locations of attached
/// documents, which are read again when such a chat continues.
/// </remarks>
public bool AllowUserToImportChats { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToImportChats, true);
/// <summary> /// <summary>
/// Should administration settings be visible in the UI? /// Should administration settings be visible in the UI?
/// </summary> /// </summary>

View File

@ -259,6 +259,12 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
// Config: allow the user to share or export plugins? // Config: allow the user to share or export plugins?
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToSharePlugins, this.Id, settingsTable, dryRun); ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToSharePlugins, this.Id, settingsTable, dryRun);
// Config: allow the user to export chats into an archive?
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToExportChats, this.Id, settingsTable, dryRun);
// Config: allow the user to import chats from an archive?
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToImportChats, this.Id, settingsTable, dryRun);
// Config: show administration settings? // Config: show administration settings?
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ShowAdminSettings, this.Id, settingsTable, dryRun); ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ShowAdminSettings, this.Id, settingsTable, dryRun);

View File

@ -43,6 +43,7 @@
- Added data backup and restore for your chats to the settings. You write the chats of the workspaces you choose into a single archive file, which you can keep as a backup or take to another computer, and you import that file again whenever you need your chats back. - Added data backup and restore for your chats to the settings. You write the chats of the workspaces you choose into a single archive file, which you can keep as a backup or take to another computer, and you import that file again whenever you need your chats back.
- Added chat backups that leave your attached documents where they are. When you back up chats in the settings, documents such as PDF or Word files are not copied into the archive. Your chats keep the place where each document is stored, so the documents stay usable wherever that place can be reached. - Added chat backups that leave your attached documents where they are. When you back up chats in the settings, documents such as PDF or Word files are not copied into the archive. Your chats keep the place where each document is stored, so the documents stay usable wherever that place can be reached.
- Added transcripts to your chat backups when you want them. When you back up chats in the settings which hold transcripts of audio or video files, AI Studio asks whether the transcripts go into the archive, because they exist nowhere else. - Added transcripts to your chat backups when you want them. When you back up chats in the settings which hold transcripts of audio or video files, AI Studio asks whether the transcripts go into the archive, because they exist nowhere else.
- Added organization-wide management for chat backups. IT departments can prevent you and your colleagues from exporting chats into an archive file, from importing chats from one, or both.
- Improved loading web content in the assistants: it now uses the same reader as the Read Web Page tool, which extracts the main content of a page more reliably and skips navigation and boilerplate. Pages from your own network, including local servers, keep working as before. When a page cannot be read, AI Studio now says why instead of leaving the field empty. - Improved loading web content in the assistants: it now uses the same reader as the Read Web Page tool, which extracts the main content of a page more reliably and skips navigation and boilerplate. Pages from your own network, including local servers, keep working as before. When a page cannot be read, AI Studio now says why instead of leaving the field empty.
- Improved the app icon. The previous one was generated by an image model; the new one was created based on it and keeps the familiar green landscape with the chat bubble. Because it is now a vector drawing, it stays sharp everywhere it appears: in your taskbar or dock, in the window list, and on the start screen while AI Studio is loading. - Improved the app icon. The previous one was generated by an image model; the new one was created based on it and keeps the familiar green landscape with the chat bubble. Because it is now a vector drawing, it stays sharp everywhere it appears: in your taskbar or dock, in the window list, and on the start screen while AI Studio is loading.
- Improved how AI Studio works out what a model can do. Every model family now stands on its own, together with the page it was read from, and our build refuses rules which contradict each other or name no source. That way, mistakes are caught before they ever reach you. - Improved how AI Studio works out what a model can do. Every model family now stands on its own, together with the page it was read from, and our build refuses rules which contradict each other or name no source. That way, mistakes are caught before they ever reach you.