Backup-Oberflaeche im Settings-Tab
Der BackupService bekommt eine Bedienoberflaeche im neuen Unterreiter "Backup". Aufbau als eigenes UserControl (UI/BackupPanel) statt direkt in frm_main.Designer.cs. Diese Datei ist ueber 1250 Zeilen gewachsene Handarbeit; ein Fehler beim Bearbeiten waere teuer. Eingehaengt wird das Panel mit vier Zeilen. Drei Bereiche: Sicherung erstellen (Zielordner, Umgang mit Zugangsdaten, Umfang), automatische Sicherung (Uhrzeit, Aufbewahrungszahl) und die Liste vorhandener Sicherungen mit Wiederherstellen, Anzeigen und Loeschen. Die Liste liest das Manifest jeder Datei und zeigt Instanz und ob Zugangsdaten enthalten sind; unlesbare oder fremde Archive werden ausgegraut mitgezeigt, statt sie zu verschweigen. Zwei Entscheidungen praegen das Verhalten. Die Passphrase wird nirgends gespeichert. Laege sie neben den Sicherungen, waere die Verschluesselung wirkungslos. Sie muss deshalb bei jeder geschuetzten Sicherung neu eingegeben und wiederholt werden, und vor dem Erstellen wird ausdruecklich bestaetigt, dass sie notiert ist — ohne sie sind die Zugangsdaten unwiederbringlich. Die Automatik sichert aus demselben Grund ohne Zugangsdaten. Wiederhergestellt wird standardmaessig in einen NEUEN Ordner neben der Instanz, nicht ueber die laufende. Diese haelt Chatverlaeufe im Speicher und die Datenbank geoeffnet — ein Ueberschreiben im Betrieb wuerde teils sofort wieder ueberschrieben und teils scheitern. Der Dialog benennt das. Vor dem Schreiben laeuft immer ein Probelauf: Er prueft die Pruefsummen und zaehlt die Konflikte, sodass ein beschaedigtes Archiv auffaellt und Ueberschreiben bestaetigt wird, bevor etwas passiert. BackupScheduler sichert taeglich zur eingestellten Uhrzeit und wendet die Rotation an. Der Zeitpunkt wird bei jedem Durchlauf neu aus den Einstellungen gelesen, damit eine Aenderung ohne Neustart greift. Die Oberflaeche ist noch nicht optisch geprueft — die Dev-Instanz haelt einen echten API-Key und wuerde beim Start Agenten ausfuehren. 385 Tests gruen (237 Core, 148 Tools). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b55512e734
commit
b51cc29667
@@ -54,5 +54,29 @@ public sealed class AppSettings
|
||||
[JsonPropertyName("openRouterBaseUrl")]
|
||||
public string OpenRouterBaseUrl { get; set; } = "https://openrouter.ai/api/v1/";
|
||||
|
||||
[Category("Backup")]
|
||||
[DisplayName("Backup-Verzeichnis")]
|
||||
[Description("Ordner, in dem Sicherungen abgelegt werden.")]
|
||||
[JsonPropertyName("backupDirectory")]
|
||||
public string BackupDirectory { get; set; } = "./Backups";
|
||||
|
||||
[Category("Backup")]
|
||||
[DisplayName("Automatisch sichern")]
|
||||
[Description("Erstellt täglich zur angegebenen Uhrzeit eine Sicherung der laufenden Instanz.")]
|
||||
[JsonPropertyName("autoBackupEnabled")]
|
||||
public bool AutoBackupEnabled { get; set; }
|
||||
|
||||
[Category("Backup")]
|
||||
[DisplayName("Uhrzeit der automatischen Sicherung")]
|
||||
[Description("Tageszeit im Format HH:mm.")]
|
||||
[JsonPropertyName("autoBackupTime")]
|
||||
public string AutoBackupTime { get; set; } = "03:00";
|
||||
|
||||
[Category("Backup")]
|
||||
[DisplayName("Aufbewahrte Sicherungen")]
|
||||
[Description("Wie viele Sicherungen je Instanz behalten werden. Ältere werden entfernt. 0 = alle behalten.")]
|
||||
[JsonPropertyName("backupKeepCount")]
|
||||
public int BackupKeepCount { get; set; } = 14;
|
||||
|
||||
public override string ToString() => "Anwendungseinstellungen";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
using ClawdDotNet.Core.Backup;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ClawdDotNet.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Erstellt einmal täglich zur eingestellten Uhrzeit eine Sicherung.
|
||||
///
|
||||
/// Bewusst ohne Zugangsdaten: Die Passphrase müsste dafür gespeichert werden, und
|
||||
/// neben den Sicherungen abgelegt wäre sie wirkungslos. Wer die Zugangsdaten
|
||||
/// mitsichern will, macht das von Hand.
|
||||
///
|
||||
/// Der Zeitpunkt wird bei jedem Durchlauf neu gegen die Einstellungen geprüft, damit
|
||||
/// eine Änderung ohne Neustart greift.
|
||||
/// </summary>
|
||||
public sealed class BackupScheduler : IDisposable
|
||||
{
|
||||
private readonly string _instanceDir;
|
||||
private readonly string _instanceName;
|
||||
private readonly SettingsManager _settings;
|
||||
private readonly ILogger _logger;
|
||||
private readonly BackupService _service = new();
|
||||
private readonly System.Windows.Forms.Timer _timer;
|
||||
|
||||
/// <summary>Verhindert mehrere Sicherungen innerhalb derselben Minute.</summary>
|
||||
private DateTime? _lastRun;
|
||||
|
||||
private bool _running;
|
||||
|
||||
public event Action<string>? OnBackupCreated;
|
||||
|
||||
public BackupScheduler(string instanceDir, string instanceName,
|
||||
SettingsManager settings, ILogger logger)
|
||||
{
|
||||
_instanceDir = instanceDir;
|
||||
_instanceName = instanceName;
|
||||
_settings = settings;
|
||||
_logger = logger;
|
||||
|
||||
// Minütlich prüfen reicht — die Uhrzeit ist auf Minuten genau eingestellt.
|
||||
_timer = new System.Windows.Forms.Timer { Interval = 60_000 };
|
||||
_timer.Tick += async (_, _) => await TickAsync();
|
||||
}
|
||||
|
||||
public void Start() => _timer.Start();
|
||||
|
||||
public void Stop() => _timer.Stop();
|
||||
|
||||
private async Task TickAsync()
|
||||
{
|
||||
if (_running)
|
||||
return;
|
||||
|
||||
var settings = _settings.AppSettings;
|
||||
if (!settings.AutoBackupEnabled)
|
||||
return;
|
||||
|
||||
if (!TimeSpan.TryParse(settings.AutoBackupTime, out var scheduled))
|
||||
return;
|
||||
|
||||
var now = DateTime.Now;
|
||||
|
||||
// Fällig, sobald die Uhrzeit erreicht ist und heute noch nichts lief.
|
||||
if (now.TimeOfDay < scheduled)
|
||||
return;
|
||||
|
||||
if (_lastRun?.Date == now.Date)
|
||||
return;
|
||||
|
||||
_running = true;
|
||||
try
|
||||
{
|
||||
await RunAsync(settings.BackupDirectory, settings.BackupKeepCount);
|
||||
_lastRun = now;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Ein Fehlschlag darf die Anwendung nicht stören; er wird protokolliert
|
||||
// und morgen erneut versucht.
|
||||
_logger.LogError(ex, "Automatische Sicherung fehlgeschlagen");
|
||||
_lastRun = now;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunAsync(string folder, int keepCount)
|
||||
{
|
||||
var safeName = string.Concat(
|
||||
_instanceName.Select(c => Path.GetInvalidFileNameChars().Contains(c) ? '_' : c));
|
||||
|
||||
var file = Path.Combine(
|
||||
Path.GetFullPath(folder),
|
||||
$"backup_{safeName}_{DateTime.Now:yyyy-MM-dd_HHmm}.zip");
|
||||
|
||||
var result = await _service.CreateAsync(_instanceDir, file, new BackupOptions
|
||||
{
|
||||
Secrets = SecretMode.Exclude,
|
||||
IncludeChatHistory = true,
|
||||
IncludeLogs = false
|
||||
});
|
||||
|
||||
_logger.LogInformation("Automatische Sicherung erstellt: {Path} ({Size} Bytes)",
|
||||
result.ZipPath, result.SizeBytes);
|
||||
|
||||
ApplyRotation(Path.GetFullPath(folder), safeName, keepCount);
|
||||
|
||||
OnBackupCreated?.Invoke(result.ZipPath);
|
||||
}
|
||||
|
||||
/// <summary>Behält die neuesten Sicherungen dieser Instanz und entfernt den Rest.</summary>
|
||||
private void ApplyRotation(string folder, string safeName, int keepCount)
|
||||
{
|
||||
if (keepCount <= 0 || !Directory.Exists(folder))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var prefix = $"backup_{safeName}_";
|
||||
|
||||
var obsolete = new DirectoryInfo(folder)
|
||||
.GetFiles("*.zip")
|
||||
.Where(f => f.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
.OrderByDescending(f => f.LastWriteTime)
|
||||
.Skip(keepCount)
|
||||
.ToList();
|
||||
|
||||
foreach (var file in obsolete)
|
||||
{
|
||||
file.Delete();
|
||||
_logger.LogInformation("Alte Sicherung entfernt: {Name}", file.Name);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Rotation der Sicherungen fehlgeschlagen");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_timer.Stop();
|
||||
_timer.Dispose();
|
||||
}
|
||||
}
|
||||
Generated
+428
@@ -0,0 +1,428 @@
|
||||
namespace ClawdDotNet.UI;
|
||||
|
||||
partial class BackupPanel
|
||||
{
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && components is not null)
|
||||
components.Dispose();
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
layoutRoot = new TableLayoutPanel();
|
||||
grp_create = new GroupBox();
|
||||
layoutCreate = new TableLayoutPanel();
|
||||
label_target = new Label();
|
||||
txt_targetFolder = new TextBox();
|
||||
btn_browseFolder = new Button();
|
||||
label_secrets = new Label();
|
||||
panel_secretMode = new FlowLayoutPanel();
|
||||
rb_secretsExclude = new RadioButton();
|
||||
rb_secretsPassphrase = new RadioButton();
|
||||
label_passphrase = new Label();
|
||||
txt_passphrase = new TextBox();
|
||||
label_passphraseRepeat = new Label();
|
||||
txt_passphraseRepeat = new TextBox();
|
||||
label_scope = new Label();
|
||||
panel_scope = new FlowLayoutPanel();
|
||||
chk_includeChatHistory = new CheckBox();
|
||||
chk_includeLogs = new CheckBox();
|
||||
panel_createActions = new FlowLayoutPanel();
|
||||
btn_createBackup = new Button();
|
||||
label_createStatus = new Label();
|
||||
|
||||
grp_auto = new GroupBox();
|
||||
layoutAuto = new TableLayoutPanel();
|
||||
chk_autoEnabled = new CheckBox();
|
||||
dtp_autoTime = new DateTimePicker();
|
||||
label_keepCount = new Label();
|
||||
num_keepCount = new NumericUpDown();
|
||||
label_autoHint = new Label();
|
||||
|
||||
grp_existing = new GroupBox();
|
||||
layoutExisting = new TableLayoutPanel();
|
||||
lv_backups = new ListView();
|
||||
col_file = new ColumnHeader();
|
||||
col_created = new ColumnHeader();
|
||||
col_instance = new ColumnHeader();
|
||||
col_size = new ColumnHeader();
|
||||
col_secrets = new ColumnHeader();
|
||||
panel_existingActions = new FlowLayoutPanel();
|
||||
btn_refresh = new Button();
|
||||
btn_restore = new Button();
|
||||
btn_showInFolder = new Button();
|
||||
btn_delete = new Button();
|
||||
|
||||
layoutRoot.SuspendLayout();
|
||||
grp_create.SuspendLayout();
|
||||
layoutCreate.SuspendLayout();
|
||||
panel_secretMode.SuspendLayout();
|
||||
panel_scope.SuspendLayout();
|
||||
panel_createActions.SuspendLayout();
|
||||
grp_auto.SuspendLayout();
|
||||
layoutAuto.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)num_keepCount).BeginInit();
|
||||
grp_existing.SuspendLayout();
|
||||
layoutExisting.SuspendLayout();
|
||||
panel_existingActions.SuspendLayout();
|
||||
SuspendLayout();
|
||||
|
||||
// ─── Wurzel ───
|
||||
layoutRoot.ColumnCount = 1;
|
||||
layoutRoot.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
layoutRoot.Dock = DockStyle.Fill;
|
||||
layoutRoot.Name = "layoutRoot";
|
||||
layoutRoot.Padding = new Padding(12);
|
||||
layoutRoot.RowCount = 3;
|
||||
layoutRoot.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||
layoutRoot.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||
layoutRoot.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||
layoutRoot.Controls.Add(grp_create, 0, 0);
|
||||
layoutRoot.Controls.Add(grp_auto, 0, 1);
|
||||
layoutRoot.Controls.Add(grp_existing, 0, 2);
|
||||
|
||||
// ═══ Sicherung erstellen ═══
|
||||
grp_create.AutoSize = true;
|
||||
grp_create.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
grp_create.Controls.Add(layoutCreate);
|
||||
grp_create.Dock = DockStyle.Fill;
|
||||
grp_create.Name = "grp_create";
|
||||
grp_create.Padding = new Padding(10);
|
||||
grp_create.TabIndex = 0;
|
||||
grp_create.TabStop = false;
|
||||
grp_create.Text = "Sicherung erstellen";
|
||||
|
||||
layoutCreate.AutoSize = true;
|
||||
layoutCreate.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
layoutCreate.ColumnCount = 3;
|
||||
layoutCreate.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
layoutCreate.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
layoutCreate.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
layoutCreate.Dock = DockStyle.Fill;
|
||||
layoutCreate.Name = "layoutCreate";
|
||||
layoutCreate.RowCount = 6;
|
||||
for (var i = 0; i < 6; i++)
|
||||
layoutCreate.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||
|
||||
label_target.Anchor = AnchorStyles.Left;
|
||||
label_target.AutoSize = true;
|
||||
label_target.Name = "label_target";
|
||||
label_target.Text = "Zielordner:";
|
||||
|
||||
txt_targetFolder.Anchor = AnchorStyles.Left | AnchorStyles.Right;
|
||||
txt_targetFolder.Name = "txt_targetFolder";
|
||||
|
||||
btn_browseFolder.AutoSize = true;
|
||||
btn_browseFolder.Name = "btn_browseFolder";
|
||||
btn_browseFolder.Text = "Durchsuchen…";
|
||||
btn_browseFolder.UseVisualStyleBackColor = true;
|
||||
|
||||
label_secrets.Anchor = AnchorStyles.Left;
|
||||
label_secrets.AutoSize = true;
|
||||
label_secrets.Name = "label_secrets";
|
||||
label_secrets.Text = "Zugangsdaten:";
|
||||
|
||||
panel_secretMode.AutoSize = true;
|
||||
panel_secretMode.Controls.Add(rb_secretsExclude);
|
||||
panel_secretMode.Controls.Add(rb_secretsPassphrase);
|
||||
panel_secretMode.Margin = new Padding(0);
|
||||
panel_secretMode.Name = "panel_secretMode";
|
||||
panel_secretMode.WrapContents = false;
|
||||
|
||||
rb_secretsExclude.AutoSize = true;
|
||||
rb_secretsExclude.Checked = true;
|
||||
rb_secretsExclude.Name = "rb_secretsExclude";
|
||||
rb_secretsExclude.TabStop = true;
|
||||
rb_secretsExclude.Text = "nicht mitsichern";
|
||||
rb_secretsExclude.UseVisualStyleBackColor = true;
|
||||
|
||||
rb_secretsPassphrase.AutoSize = true;
|
||||
rb_secretsPassphrase.Name = "rb_secretsPassphrase";
|
||||
rb_secretsPassphrase.Text = "mit Passphrase schützen";
|
||||
rb_secretsPassphrase.UseVisualStyleBackColor = true;
|
||||
|
||||
label_passphrase.Anchor = AnchorStyles.Left;
|
||||
label_passphrase.AutoSize = true;
|
||||
label_passphrase.Name = "label_passphrase";
|
||||
label_passphrase.Text = "Passphrase:";
|
||||
|
||||
txt_passphrase.Anchor = AnchorStyles.Left | AnchorStyles.Right;
|
||||
txt_passphrase.Name = "txt_passphrase";
|
||||
txt_passphrase.UseSystemPasswordChar = true;
|
||||
|
||||
label_passphraseRepeat.Anchor = AnchorStyles.Left;
|
||||
label_passphraseRepeat.AutoSize = true;
|
||||
label_passphraseRepeat.Name = "label_passphraseRepeat";
|
||||
label_passphraseRepeat.Text = "Wiederholen:";
|
||||
|
||||
txt_passphraseRepeat.Anchor = AnchorStyles.Left | AnchorStyles.Right;
|
||||
txt_passphraseRepeat.Name = "txt_passphraseRepeat";
|
||||
txt_passphraseRepeat.UseSystemPasswordChar = true;
|
||||
|
||||
label_scope.Anchor = AnchorStyles.Left;
|
||||
label_scope.AutoSize = true;
|
||||
label_scope.Name = "label_scope";
|
||||
label_scope.Text = "Umfang:";
|
||||
|
||||
panel_scope.AutoSize = true;
|
||||
panel_scope.Controls.Add(chk_includeChatHistory);
|
||||
panel_scope.Controls.Add(chk_includeLogs);
|
||||
panel_scope.Margin = new Padding(0);
|
||||
panel_scope.Name = "panel_scope";
|
||||
panel_scope.WrapContents = false;
|
||||
|
||||
chk_includeChatHistory.AutoSize = true;
|
||||
chk_includeChatHistory.Checked = true;
|
||||
chk_includeChatHistory.CheckState = CheckState.Checked;
|
||||
chk_includeChatHistory.Name = "chk_includeChatHistory";
|
||||
chk_includeChatHistory.Text = "Chatverläufe";
|
||||
chk_includeChatHistory.UseVisualStyleBackColor = true;
|
||||
|
||||
chk_includeLogs.AutoSize = true;
|
||||
chk_includeLogs.Name = "chk_includeLogs";
|
||||
chk_includeLogs.Text = "Protokolle";
|
||||
chk_includeLogs.UseVisualStyleBackColor = true;
|
||||
|
||||
panel_createActions.AutoSize = true;
|
||||
panel_createActions.Controls.Add(btn_createBackup);
|
||||
panel_createActions.Controls.Add(label_createStatus);
|
||||
panel_createActions.Margin = new Padding(0, 8, 0, 0);
|
||||
panel_createActions.Name = "panel_createActions";
|
||||
panel_createActions.WrapContents = false;
|
||||
|
||||
btn_createBackup.AutoSize = true;
|
||||
btn_createBackup.Name = "btn_createBackup";
|
||||
btn_createBackup.Padding = new Padding(8, 2, 8, 2);
|
||||
btn_createBackup.Text = "Sicherung jetzt erstellen";
|
||||
btn_createBackup.UseVisualStyleBackColor = true;
|
||||
|
||||
label_createStatus.Anchor = AnchorStyles.Left;
|
||||
label_createStatus.AutoSize = true;
|
||||
label_createStatus.Margin = new Padding(12, 8, 3, 0);
|
||||
label_createStatus.Name = "label_createStatus";
|
||||
|
||||
layoutCreate.Controls.Add(label_target, 0, 0);
|
||||
layoutCreate.Controls.Add(txt_targetFolder, 1, 0);
|
||||
layoutCreate.Controls.Add(btn_browseFolder, 2, 0);
|
||||
layoutCreate.Controls.Add(label_secrets, 0, 1);
|
||||
layoutCreate.Controls.Add(panel_secretMode, 1, 1);
|
||||
layoutCreate.Controls.Add(label_passphrase, 0, 2);
|
||||
layoutCreate.Controls.Add(txt_passphrase, 1, 2);
|
||||
layoutCreate.Controls.Add(label_passphraseRepeat, 0, 3);
|
||||
layoutCreate.Controls.Add(txt_passphraseRepeat, 1, 3);
|
||||
layoutCreate.Controls.Add(label_scope, 0, 4);
|
||||
layoutCreate.Controls.Add(panel_scope, 1, 4);
|
||||
layoutCreate.Controls.Add(panel_createActions, 1, 5);
|
||||
|
||||
// ═══ Automatik ═══
|
||||
grp_auto.AutoSize = true;
|
||||
grp_auto.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
grp_auto.Controls.Add(layoutAuto);
|
||||
grp_auto.Dock = DockStyle.Fill;
|
||||
grp_auto.Margin = new Padding(3, 10, 3, 3);
|
||||
grp_auto.Name = "grp_auto";
|
||||
grp_auto.Padding = new Padding(10);
|
||||
grp_auto.TabIndex = 1;
|
||||
grp_auto.TabStop = false;
|
||||
grp_auto.Text = "Automatische Sicherung";
|
||||
|
||||
layoutAuto.AutoSize = true;
|
||||
layoutAuto.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
layoutAuto.ColumnCount = 5;
|
||||
for (var i = 0; i < 4; i++)
|
||||
layoutAuto.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
layoutAuto.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
layoutAuto.Dock = DockStyle.Fill;
|
||||
layoutAuto.Name = "layoutAuto";
|
||||
layoutAuto.RowCount = 2;
|
||||
layoutAuto.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||
layoutAuto.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||
|
||||
chk_autoEnabled.Anchor = AnchorStyles.Left;
|
||||
chk_autoEnabled.AutoSize = true;
|
||||
chk_autoEnabled.Name = "chk_autoEnabled";
|
||||
chk_autoEnabled.Text = "täglich sichern um";
|
||||
chk_autoEnabled.UseVisualStyleBackColor = true;
|
||||
|
||||
dtp_autoTime.Anchor = AnchorStyles.Left;
|
||||
dtp_autoTime.Format = DateTimePickerFormat.Time;
|
||||
dtp_autoTime.Name = "dtp_autoTime";
|
||||
dtp_autoTime.ShowUpDown = true;
|
||||
dtp_autoTime.Width = 90;
|
||||
|
||||
label_keepCount.Anchor = AnchorStyles.Left;
|
||||
label_keepCount.AutoSize = true;
|
||||
label_keepCount.Margin = new Padding(24, 6, 3, 0);
|
||||
label_keepCount.Name = "label_keepCount";
|
||||
label_keepCount.Text = "Aufbewahren:";
|
||||
|
||||
num_keepCount.Anchor = AnchorStyles.Left;
|
||||
num_keepCount.Maximum = 999;
|
||||
num_keepCount.Name = "num_keepCount";
|
||||
num_keepCount.Width = 70;
|
||||
|
||||
label_autoHint.Anchor = AnchorStyles.Left;
|
||||
label_autoHint.AutoSize = true;
|
||||
label_autoHint.ForeColor = SystemColors.GrayText;
|
||||
label_autoHint.Name = "label_autoHint";
|
||||
label_autoHint.Text = "Die Automatik sichert ohne Zugangsdaten — eine gespeicherte Passphrase "
|
||||
+ "neben den Sicherungen wäre wirkungslos.";
|
||||
|
||||
layoutAuto.Controls.Add(chk_autoEnabled, 0, 0);
|
||||
layoutAuto.Controls.Add(dtp_autoTime, 1, 0);
|
||||
layoutAuto.Controls.Add(label_keepCount, 2, 0);
|
||||
layoutAuto.Controls.Add(num_keepCount, 3, 0);
|
||||
layoutAuto.Controls.Add(label_autoHint, 0, 1);
|
||||
layoutAuto.SetColumnSpan(label_autoHint, 5);
|
||||
|
||||
// ═══ Vorhandene Sicherungen ═══
|
||||
grp_existing.Controls.Add(layoutExisting);
|
||||
grp_existing.Dock = DockStyle.Fill;
|
||||
grp_existing.Margin = new Padding(3, 10, 3, 3);
|
||||
grp_existing.Name = "grp_existing";
|
||||
grp_existing.Padding = new Padding(10);
|
||||
grp_existing.TabIndex = 2;
|
||||
grp_existing.TabStop = false;
|
||||
grp_existing.Text = "Vorhandene Sicherungen";
|
||||
|
||||
layoutExisting.ColumnCount = 1;
|
||||
layoutExisting.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
layoutExisting.Dock = DockStyle.Fill;
|
||||
layoutExisting.Name = "layoutExisting";
|
||||
layoutExisting.RowCount = 2;
|
||||
layoutExisting.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||
layoutExisting.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||
layoutExisting.Controls.Add(lv_backups, 0, 0);
|
||||
layoutExisting.Controls.Add(panel_existingActions, 0, 1);
|
||||
|
||||
lv_backups.Columns.AddRange(new[] { col_file, col_created, col_instance, col_size, col_secrets });
|
||||
lv_backups.Dock = DockStyle.Fill;
|
||||
lv_backups.FullRowSelect = true;
|
||||
lv_backups.MultiSelect = false;
|
||||
lv_backups.Name = "lv_backups";
|
||||
lv_backups.UseCompatibleStateImageBehavior = false;
|
||||
lv_backups.View = View.Details;
|
||||
|
||||
col_file.Text = "Datei";
|
||||
col_file.Width = 420;
|
||||
col_created.Text = "Erstellt";
|
||||
col_created.Width = 150;
|
||||
col_instance.Text = "Instanz";
|
||||
col_instance.Width = 180;
|
||||
col_size.Text = "Größe";
|
||||
col_size.Width = 100;
|
||||
col_secrets.Text = "Zugangsdaten";
|
||||
col_secrets.Width = 140;
|
||||
|
||||
panel_existingActions.AutoSize = true;
|
||||
panel_existingActions.Controls.Add(btn_refresh);
|
||||
panel_existingActions.Controls.Add(btn_restore);
|
||||
panel_existingActions.Controls.Add(btn_showInFolder);
|
||||
panel_existingActions.Controls.Add(btn_delete);
|
||||
panel_existingActions.Margin = new Padding(0, 8, 0, 0);
|
||||
panel_existingActions.Name = "panel_existingActions";
|
||||
panel_existingActions.WrapContents = false;
|
||||
|
||||
btn_refresh.AutoSize = true;
|
||||
btn_refresh.Name = "btn_refresh";
|
||||
btn_refresh.Text = "Aktualisieren";
|
||||
btn_refresh.UseVisualStyleBackColor = true;
|
||||
|
||||
btn_restore.AutoSize = true;
|
||||
btn_restore.Name = "btn_restore";
|
||||
btn_restore.Padding = new Padding(8, 2, 8, 2);
|
||||
btn_restore.Text = "Wiederherstellen…";
|
||||
btn_restore.UseVisualStyleBackColor = true;
|
||||
|
||||
btn_showInFolder.AutoSize = true;
|
||||
btn_showInFolder.Name = "btn_showInFolder";
|
||||
btn_showInFolder.Text = "Im Ordner zeigen";
|
||||
btn_showInFolder.UseVisualStyleBackColor = true;
|
||||
|
||||
btn_delete.AutoSize = true;
|
||||
btn_delete.Name = "btn_delete";
|
||||
btn_delete.Text = "Löschen";
|
||||
btn_delete.UseVisualStyleBackColor = true;
|
||||
|
||||
// ─── Steuerelement ───
|
||||
AutoScaleDimensions = new SizeF(10F, 25F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
Controls.Add(layoutRoot);
|
||||
Name = "BackupPanel";
|
||||
Size = new Size(1200, 800);
|
||||
|
||||
layoutRoot.ResumeLayout(false);
|
||||
layoutRoot.PerformLayout();
|
||||
grp_create.ResumeLayout(false);
|
||||
grp_create.PerformLayout();
|
||||
layoutCreate.ResumeLayout(false);
|
||||
layoutCreate.PerformLayout();
|
||||
panel_secretMode.ResumeLayout(false);
|
||||
panel_secretMode.PerformLayout();
|
||||
panel_scope.ResumeLayout(false);
|
||||
panel_scope.PerformLayout();
|
||||
panel_createActions.ResumeLayout(false);
|
||||
panel_createActions.PerformLayout();
|
||||
grp_auto.ResumeLayout(false);
|
||||
grp_auto.PerformLayout();
|
||||
layoutAuto.ResumeLayout(false);
|
||||
layoutAuto.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)num_keepCount).EndInit();
|
||||
grp_existing.ResumeLayout(false);
|
||||
layoutExisting.ResumeLayout(false);
|
||||
layoutExisting.PerformLayout();
|
||||
panel_existingActions.ResumeLayout(false);
|
||||
panel_existingActions.PerformLayout();
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
private TableLayoutPanel layoutRoot;
|
||||
|
||||
private GroupBox grp_create;
|
||||
private TableLayoutPanel layoutCreate;
|
||||
private Label label_target;
|
||||
private TextBox txt_targetFolder;
|
||||
private Button btn_browseFolder;
|
||||
private Label label_secrets;
|
||||
private FlowLayoutPanel panel_secretMode;
|
||||
private RadioButton rb_secretsExclude;
|
||||
private RadioButton rb_secretsPassphrase;
|
||||
private Label label_passphrase;
|
||||
private TextBox txt_passphrase;
|
||||
private Label label_passphraseRepeat;
|
||||
private TextBox txt_passphraseRepeat;
|
||||
private Label label_scope;
|
||||
private FlowLayoutPanel panel_scope;
|
||||
private CheckBox chk_includeChatHistory;
|
||||
private CheckBox chk_includeLogs;
|
||||
private FlowLayoutPanel panel_createActions;
|
||||
private Button btn_createBackup;
|
||||
private Label label_createStatus;
|
||||
|
||||
private GroupBox grp_auto;
|
||||
private TableLayoutPanel layoutAuto;
|
||||
private CheckBox chk_autoEnabled;
|
||||
private DateTimePicker dtp_autoTime;
|
||||
private Label label_keepCount;
|
||||
private NumericUpDown num_keepCount;
|
||||
private Label label_autoHint;
|
||||
|
||||
private GroupBox grp_existing;
|
||||
private TableLayoutPanel layoutExisting;
|
||||
private ListView lv_backups;
|
||||
private ColumnHeader col_file;
|
||||
private ColumnHeader col_created;
|
||||
private ColumnHeader col_instance;
|
||||
private ColumnHeader col_size;
|
||||
private ColumnHeader col_secrets;
|
||||
private FlowLayoutPanel panel_existingActions;
|
||||
private Button btn_refresh;
|
||||
private Button btn_restore;
|
||||
private Button btn_showInFolder;
|
||||
private Button btn_delete;
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
using ClawdDotNet.Core.Backup;
|
||||
using ClawdDotNet.Models;
|
||||
using ClawdDotNet.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ClawdDotNet.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Bedienoberfläche für Sicherung und Wiederherstellung einer Instanz.
|
||||
///
|
||||
/// Zwei Entscheidungen prägen das Verhalten:
|
||||
///
|
||||
/// Die Passphrase wird nirgends gespeichert. Läge sie neben den Sicherungen, wäre die
|
||||
/// Verschlüsselung wirkungslos. Deshalb muss sie bei jeder geschützten Sicherung neu
|
||||
/// eingegeben werden, und die Automatik sichert ohne Zugangsdaten.
|
||||
///
|
||||
/// Wiederhergestellt wird standardmäßig in einen NEUEN Ordner, nicht über die laufende
|
||||
/// Instanz. Diese hält Chatverläufe im Speicher und die Datenbank geöffnet — ein
|
||||
/// Überschreiben im Betrieb würde teils sofort wieder überschrieben und teils
|
||||
/// scheitern.
|
||||
/// </summary>
|
||||
public sealed partial class BackupPanel : UserControl
|
||||
{
|
||||
private readonly BackupService _service = new();
|
||||
|
||||
private SettingsManager? _settingsManager;
|
||||
private ILogger? _logger;
|
||||
private string _instanceDir = "";
|
||||
private string _instanceName = "";
|
||||
private bool _loading;
|
||||
|
||||
public BackupPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
WireEvents();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aktualisiert die Liste von außen — etwa nachdem die Automatik gesichert hat.
|
||||
/// </summary>
|
||||
public void RefreshFromOutside()
|
||||
{
|
||||
if (!IsHandleCreated || IsDisposed)
|
||||
return;
|
||||
|
||||
RefreshBackupList();
|
||||
}
|
||||
|
||||
/// <summary>Wird vom Hauptfenster nach dem Laden der Instanz aufgerufen.</summary>
|
||||
public void Initialize(string instanceDir, string instanceName,
|
||||
SettingsManager settingsManager, ILogger logger)
|
||||
{
|
||||
_instanceDir = instanceDir;
|
||||
_instanceName = instanceName;
|
||||
_settingsManager = settingsManager;
|
||||
_logger = logger;
|
||||
|
||||
LoadSettings();
|
||||
UpdateEnabledState();
|
||||
RefreshBackupList();
|
||||
}
|
||||
|
||||
private void WireEvents()
|
||||
{
|
||||
btn_browseFolder.Click += OnBrowseFolder;
|
||||
btn_createBackup.Click += async (_, _) => await CreateBackupAsync();
|
||||
btn_refresh.Click += (_, _) => RefreshBackupList();
|
||||
btn_restore.Click += async (_, _) => await RestoreSelectedAsync();
|
||||
btn_showInFolder.Click += (_, _) => ShowSelectedInFolder();
|
||||
btn_delete.Click += (_, _) => DeleteSelected();
|
||||
|
||||
rb_secretsExclude.CheckedChanged += (_, _) => UpdateEnabledState();
|
||||
rb_secretsPassphrase.CheckedChanged += (_, _) => UpdateEnabledState();
|
||||
|
||||
chk_autoEnabled.CheckedChanged += (_, _) => { UpdateEnabledState(); SaveSettings(); };
|
||||
dtp_autoTime.ValueChanged += (_, _) => SaveSettings();
|
||||
num_keepCount.ValueChanged += (_, _) => SaveSettings();
|
||||
txt_targetFolder.Leave += (_, _) => SaveSettings();
|
||||
|
||||
lv_backups.SelectedIndexChanged += (_, _) => UpdateEnabledState();
|
||||
lv_backups.DoubleClick += async (_, _) => await RestoreSelectedAsync();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════
|
||||
// Einstellungen
|
||||
// ═══════════════════════════════════════════════════
|
||||
|
||||
private void LoadSettings()
|
||||
{
|
||||
if (_settingsManager is null) return;
|
||||
|
||||
_loading = true;
|
||||
try
|
||||
{
|
||||
var settings = _settingsManager.AppSettings;
|
||||
|
||||
txt_targetFolder.Text = Path.GetFullPath(settings.BackupDirectory);
|
||||
chk_autoEnabled.Checked = settings.AutoBackupEnabled;
|
||||
num_keepCount.Value = Math.Clamp(settings.BackupKeepCount, 0, 999);
|
||||
|
||||
dtp_autoTime.Value = TimeSpan.TryParse(settings.AutoBackupTime, out var time)
|
||||
? DateTime.Today.Add(time)
|
||||
: DateTime.Today.AddHours(3);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
if (_loading || _settingsManager is null) return;
|
||||
|
||||
var settings = _settingsManager.AppSettings;
|
||||
settings.BackupDirectory = txt_targetFolder.Text.Trim();
|
||||
settings.AutoBackupEnabled = chk_autoEnabled.Checked;
|
||||
settings.AutoBackupTime = dtp_autoTime.Value.ToString("HH:mm");
|
||||
settings.BackupKeepCount = (int)num_keepCount.Value;
|
||||
|
||||
_settingsManager.Save();
|
||||
}
|
||||
|
||||
private void UpdateEnabledState()
|
||||
{
|
||||
var withPassphrase = rb_secretsPassphrase.Checked;
|
||||
label_passphrase.Enabled = withPassphrase;
|
||||
txt_passphrase.Enabled = withPassphrase;
|
||||
label_passphraseRepeat.Enabled = withPassphrase;
|
||||
txt_passphraseRepeat.Enabled = withPassphrase;
|
||||
|
||||
dtp_autoTime.Enabled = chk_autoEnabled.Checked;
|
||||
num_keepCount.Enabled = chk_autoEnabled.Checked;
|
||||
|
||||
var hasSelection = lv_backups.SelectedItems.Count > 0;
|
||||
btn_restore.Enabled = hasSelection;
|
||||
btn_showInFolder.Enabled = hasSelection;
|
||||
btn_delete.Enabled = hasSelection;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════
|
||||
// Sichern
|
||||
// ═══════════════════════════════════════════════════
|
||||
|
||||
private void OnBrowseFolder(object? sender, EventArgs e)
|
||||
{
|
||||
using var dialog = new FolderBrowserDialog
|
||||
{
|
||||
Description = "Ordner für Sicherungen wählen",
|
||||
UseDescriptionForTitle = true,
|
||||
SelectedPath = Directory.Exists(txt_targetFolder.Text)
|
||||
? txt_targetFolder.Text
|
||||
: Environment.CurrentDirectory
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog(this) == DialogResult.OK)
|
||||
{
|
||||
txt_targetFolder.Text = dialog.SelectedPath;
|
||||
SaveSettings();
|
||||
RefreshBackupList();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateBackupAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_instanceDir))
|
||||
return;
|
||||
|
||||
var options = BuildOptions();
|
||||
if (options is null)
|
||||
return; // Eingabefehler, bereits gemeldet
|
||||
|
||||
var folder = txt_targetFolder.Text.Trim();
|
||||
var file = Path.Combine(folder, BuildFileName());
|
||||
|
||||
btn_createBackup.Enabled = false;
|
||||
SetStatus("Sicherung läuft…", SystemColors.GrayText);
|
||||
|
||||
try
|
||||
{
|
||||
var result = await Task.Run(() => _service.CreateAsync(_instanceDir, file, options));
|
||||
|
||||
SetStatus($"Fertig: {Path.GetFileName(result.ZipPath)} ({FormatSize(result.SizeBytes)})",
|
||||
Color.FromArgb(0, 128, 0));
|
||||
|
||||
_logger?.LogInformation("Sicherung erstellt: {Path} ({Size} Bytes)",
|
||||
result.ZipPath, result.SizeBytes);
|
||||
|
||||
txt_passphrase.Clear();
|
||||
txt_passphraseRepeat.Clear();
|
||||
|
||||
ApplyRotation(folder);
|
||||
RefreshBackupList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetStatus("Fehlgeschlagen", Color.Firebrick);
|
||||
_logger?.LogError(ex, "Sicherung fehlgeschlagen");
|
||||
MessageBox.Show(this, $"Die Sicherung ist fehlgeschlagen:\n\n{ex.Message}",
|
||||
"Sicherung", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
btn_createBackup.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Liest die Eingaben. Gibt null zurück, wenn etwas fehlt.</summary>
|
||||
private BackupOptions? BuildOptions()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(txt_targetFolder.Text))
|
||||
{
|
||||
MessageBox.Show(this, "Bitte einen Zielordner angeben.",
|
||||
"Sicherung", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!rb_secretsPassphrase.Checked)
|
||||
{
|
||||
return new BackupOptions
|
||||
{
|
||||
Secrets = SecretMode.Exclude,
|
||||
IncludeLogs = chk_includeLogs.Checked,
|
||||
IncludeChatHistory = chk_includeChatHistory.Checked
|
||||
};
|
||||
}
|
||||
|
||||
var passphrase = txt_passphrase.Text;
|
||||
|
||||
if (string.IsNullOrEmpty(passphrase))
|
||||
{
|
||||
MessageBox.Show(this, "Bitte eine Passphrase eingeben.",
|
||||
"Sicherung", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
txt_passphrase.Focus();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (passphrase != txt_passphraseRepeat.Text)
|
||||
{
|
||||
MessageBox.Show(this, "Die beiden Passphrasen stimmen nicht überein.",
|
||||
"Sicherung", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
txt_passphraseRepeat.Focus();
|
||||
txt_passphraseRepeat.SelectAll();
|
||||
return null;
|
||||
}
|
||||
|
||||
// Eine vergessene Passphrase bedeutet unwiederbringlich verlorene Zugangsdaten.
|
||||
var answer = MessageBox.Show(this,
|
||||
"Ohne diese Passphrase lassen sich die Zugangsdaten in der Sicherung " +
|
||||
"nicht wiederherstellen. Es gibt keinen Ersatzweg.\n\n" +
|
||||
"Passphrase sicher notiert?",
|
||||
"Passphrase bestätigen", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
|
||||
|
||||
if (answer != DialogResult.Yes)
|
||||
return null;
|
||||
|
||||
return new BackupOptions
|
||||
{
|
||||
Secrets = SecretMode.Passphrase,
|
||||
Passphrase = passphrase,
|
||||
IncludeLogs = chk_includeLogs.Checked,
|
||||
IncludeChatHistory = chk_includeChatHistory.Checked
|
||||
};
|
||||
}
|
||||
|
||||
private string BuildFileName()
|
||||
{
|
||||
var safeName = string.Concat(
|
||||
(_instanceName.Length > 0 ? _instanceName : "Instanz")
|
||||
.Select(c => Path.GetInvalidFileNameChars().Contains(c) ? '_' : c));
|
||||
|
||||
return $"backup_{safeName}_{DateTime.Now:yyyy-MM-dd_HHmm}.zip";
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════
|
||||
// Liste
|
||||
// ═══════════════════════════════════════════════════
|
||||
|
||||
private void RefreshBackupList()
|
||||
{
|
||||
lv_backups.BeginUpdate();
|
||||
lv_backups.Items.Clear();
|
||||
|
||||
try
|
||||
{
|
||||
var folder = txt_targetFolder.Text.Trim();
|
||||
if (!Directory.Exists(folder))
|
||||
return;
|
||||
|
||||
var files = new DirectoryInfo(folder)
|
||||
.GetFiles("*.zip")
|
||||
.OrderByDescending(f => f.LastWriteTime);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var item = new ListViewItem(file.Name) { Tag = file.FullName };
|
||||
item.SubItems.Add(file.LastWriteTime.ToString("g"));
|
||||
|
||||
// Das Manifest verrät Instanz und ob Zugangsdaten enthalten sind.
|
||||
try
|
||||
{
|
||||
var manifest = _service.InspectAsync(file.FullName).GetAwaiter().GetResult();
|
||||
item.SubItems.Add(manifest.InstanceName);
|
||||
item.SubItems.Add(FormatSize(file.Length));
|
||||
item.SubItems.Add(manifest.HasSecrets
|
||||
? $"ja ({manifest.SecretCount}, Passphrase)"
|
||||
: "nein");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Fremde oder beschädigte Archive trotzdem anzeigen — der Benutzer
|
||||
// soll sehen, was im Ordner liegt.
|
||||
item.SubItems.Add("—");
|
||||
item.SubItems.Add(FormatSize(file.Length));
|
||||
item.SubItems.Add("unlesbar");
|
||||
item.ForeColor = SystemColors.GrayText;
|
||||
}
|
||||
|
||||
lv_backups.Items.Add(item);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
lv_backups.EndUpdate();
|
||||
UpdateEnabledState();
|
||||
}
|
||||
}
|
||||
|
||||
private string? SelectedBackupPath
|
||||
=> lv_backups.SelectedItems.Count > 0
|
||||
? lv_backups.SelectedItems[0].Tag as string
|
||||
: null;
|
||||
|
||||
private void ShowSelectedInFolder()
|
||||
{
|
||||
if (SelectedBackupPath is not { } path) return;
|
||||
|
||||
System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{path}\"");
|
||||
}
|
||||
|
||||
private void DeleteSelected()
|
||||
{
|
||||
if (SelectedBackupPath is not { } path) return;
|
||||
|
||||
var answer = MessageBox.Show(this,
|
||||
$"Sicherung endgültig löschen?\n\n{Path.GetFileName(path)}",
|
||||
"Löschen", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
|
||||
|
||||
if (answer != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(path);
|
||||
RefreshBackupList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(this, $"Konnte nicht gelöscht werden:\n\n{ex.Message}",
|
||||
"Löschen", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════
|
||||
// Wiederherstellen
|
||||
// ═══════════════════════════════════════════════════
|
||||
|
||||
private async Task RestoreSelectedAsync()
|
||||
{
|
||||
if (SelectedBackupPath is not { } zipPath) return;
|
||||
|
||||
BackupManifest manifest;
|
||||
try
|
||||
{
|
||||
manifest = await _service.InspectAsync(zipPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(this, $"Die Sicherung ist nicht lesbar:\n\n{ex.Message}",
|
||||
"Wiederherstellen", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
using var dialog = new frm_RestoreBackup(manifest, SuggestRestoreTarget(manifest));
|
||||
if (dialog.ShowDialog(this) != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var options = new RestoreOptions
|
||||
{
|
||||
Passphrase = dialog.Passphrase,
|
||||
Overwrite = dialog.Overwrite
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Erst ohne zu schreiben prüfen — Prüfsummen und Konflikte.
|
||||
var preview = await Task.Run(() =>
|
||||
_service.RestoreAsync(zipPath, dialog.TargetDirectory,
|
||||
options with { DryRun = true, Overwrite = true }));
|
||||
|
||||
if (preview.WouldOverwrite.Count > 0 && !dialog.Overwrite)
|
||||
{
|
||||
var answer = MessageBox.Show(this,
|
||||
$"Im Zielordner liegen bereits {preview.WouldOverwrite.Count} der enthaltenen " +
|
||||
"Dateien.\n\nVorhandene Dateien überschreiben?",
|
||||
"Wiederherstellen", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning);
|
||||
|
||||
if (answer == DialogResult.Cancel)
|
||||
return;
|
||||
|
||||
options = options with { Overwrite = answer == DialogResult.Yes };
|
||||
}
|
||||
|
||||
var result = await Task.Run(() =>
|
||||
_service.RestoreAsync(zipPath, dialog.TargetDirectory, options));
|
||||
|
||||
_logger?.LogInformation("Wiederherstellung nach {Target}: {Written} Dateien, {Skipped} übersprungen",
|
||||
dialog.TargetDirectory, result.Written.Count, result.Skipped.Count);
|
||||
|
||||
var summary = $"{result.Written.Count} Datei(en) wiederhergestellt nach:\n{dialog.TargetDirectory}";
|
||||
if (result.Skipped.Count > 0)
|
||||
summary += $"\n\n{result.Skipped.Count} vorhandene Datei(en) blieben unangetastet.";
|
||||
|
||||
if (!manifest.HasSecrets && manifest.SecretCount > 0)
|
||||
summary += "\n\nHinweis: Diese Sicherung enthält keine Zugangsdaten. " +
|
||||
"API-Schlüssel und Passwörter müssen neu eingetragen werden.";
|
||||
|
||||
summary += "\n\nDie wiederhergestellte Instanz wird erst nach einem Neustart " +
|
||||
"der Anwendung sichtbar.";
|
||||
|
||||
MessageBox.Show(this, summary, "Wiederherstellen",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "Wiederherstellung fehlgeschlagen");
|
||||
MessageBox.Show(this, $"Die Wiederherstellung ist fehlgeschlagen:\n\n{ex.Message}",
|
||||
"Wiederherstellen", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schlägt einen neuen Ordner neben der Instanz vor, nicht die laufende Instanz
|
||||
/// selbst — diese hält Dateien geöffnet und würde Teile sofort überschreiben.
|
||||
/// </summary>
|
||||
private string SuggestRestoreTarget(BackupManifest manifest)
|
||||
{
|
||||
var parent = Directory.GetParent(_instanceDir)?.FullName ?? _instanceDir;
|
||||
var name = string.IsNullOrWhiteSpace(manifest.InstanceName)
|
||||
? "Instanz"
|
||||
: manifest.InstanceName;
|
||||
|
||||
var safeName = string.Concat(
|
||||
name.Select(c => Path.GetInvalidFileNameChars().Contains(c) ? '_' : c));
|
||||
|
||||
return Path.Combine(parent, $"Instance-{safeName}_wiederhergestellt_{DateTime.Now:yyyyMMdd_HHmm}");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════
|
||||
// Rotation
|
||||
// ═══════════════════════════════════════════════════
|
||||
|
||||
/// <summary>Entfernt die ältesten Sicherungen dieser Instanz über der Aufbewahrungszahl.</summary>
|
||||
private void ApplyRotation(string folder)
|
||||
{
|
||||
var keep = (int)num_keepCount.Value;
|
||||
if (keep <= 0 || !Directory.Exists(folder))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var prefix = $"backup_{_instanceName}_";
|
||||
|
||||
var obsolete = new DirectoryInfo(folder)
|
||||
.GetFiles("*.zip")
|
||||
.Where(f => f.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
.OrderByDescending(f => f.LastWriteTime)
|
||||
.Skip(keep)
|
||||
.ToList();
|
||||
|
||||
foreach (var file in obsolete)
|
||||
{
|
||||
file.Delete();
|
||||
_logger?.LogInformation("Alte Sicherung entfernt: {Name}", file.Name);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Aufräumen darf eine erfolgreiche Sicherung nicht nachträglich entwerten.
|
||||
_logger?.LogWarning(ex, "Rotation der Sicherungen fehlgeschlagen");
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════
|
||||
// Kleinkram
|
||||
// ═══════════════════════════════════════════════════
|
||||
|
||||
private void SetStatus(string text, Color color)
|
||||
{
|
||||
label_createStatus.Text = text;
|
||||
label_createStatus.ForeColor = color;
|
||||
}
|
||||
|
||||
private static string FormatSize(long bytes) => bytes switch
|
||||
{
|
||||
< 1024 => $"{bytes} B",
|
||||
< 1024 * 1024 => $"{bytes / 1024.0:F1} KB",
|
||||
< 1024L * 1024 * 1024 => $"{bytes / 1024.0 / 1024:F1} MB",
|
||||
_ => $"{bytes / 1024.0 / 1024 / 1024:F2} GB"
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
using ClawdDotNet.Core.Backup;
|
||||
|
||||
namespace ClawdDotNet;
|
||||
|
||||
/// <summary>
|
||||
/// Fragt ab, wohin wiederhergestellt wird und — falls nötig — die Passphrase.
|
||||
///
|
||||
/// Bewusst ein eigener Schritt statt stiller Vorbelegung: Eine Wiederherstellung
|
||||
/// überschreibt Arbeitsstände, das soll niemand versehentlich auslösen.
|
||||
/// </summary>
|
||||
public sealed class frm_RestoreBackup : Form
|
||||
{
|
||||
private readonly TextBox _txtTarget = new();
|
||||
private readonly TextBox _txtPassphrase = new() { UseSystemPasswordChar = true };
|
||||
private readonly CheckBox _chkOverwrite = new();
|
||||
|
||||
public string TargetDirectory => _txtTarget.Text.Trim();
|
||||
public string? Passphrase => _txtPassphrase.Text.Length > 0 ? _txtPassphrase.Text : null;
|
||||
public bool Overwrite => _chkOverwrite.Checked;
|
||||
|
||||
public frm_RestoreBackup(BackupManifest manifest, string suggestedTarget)
|
||||
{
|
||||
Text = "Sicherung wiederherstellen";
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
StartPosition = FormStartPosition.CenterParent;
|
||||
MinimizeBox = false;
|
||||
MaximizeBox = false;
|
||||
ClientSize = new Size(680, 380);
|
||||
|
||||
var layout = new TableLayoutPanel
|
||||
{
|
||||
ColumnCount = 3,
|
||||
Dock = DockStyle.Fill,
|
||||
Padding = new Padding(14),
|
||||
RowCount = 7
|
||||
};
|
||||
layout.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
layout.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
||||
for (var i = 0; i < 7; i++)
|
||||
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||
|
||||
// ─── Beschreibung der Sicherung ───
|
||||
var info = new Label
|
||||
{
|
||||
AutoSize = true,
|
||||
Margin = new Padding(0, 0, 0, 12),
|
||||
Text = $"Instanz: {manifest.InstanceName}\n"
|
||||
+ $"Erstellt: {manifest.CreatedAt:g}\n"
|
||||
+ $"Dateien: {manifest.Files.Count}\n"
|
||||
+ $"Zugangsdaten: {DescribeSecrets(manifest)}"
|
||||
};
|
||||
layout.Controls.Add(info, 0, 0);
|
||||
layout.SetColumnSpan(info, 3);
|
||||
|
||||
// ─── Zielordner ───
|
||||
layout.Controls.Add(new Label { Anchor = AnchorStyles.Left, AutoSize = true, Text = "Zielordner:" }, 0, 1);
|
||||
|
||||
_txtTarget.Anchor = AnchorStyles.Left | AnchorStyles.Right;
|
||||
_txtTarget.Text = suggestedTarget;
|
||||
layout.Controls.Add(_txtTarget, 1, 1);
|
||||
|
||||
var browse = new Button { AutoSize = true, Text = "…" };
|
||||
browse.Click += OnBrowse;
|
||||
layout.Controls.Add(browse, 2, 1);
|
||||
|
||||
var targetHint = new Label
|
||||
{
|
||||
AutoSize = true,
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Margin = new Padding(0, 2, 0, 10),
|
||||
MaximumSize = new Size(620, 0),
|
||||
Text = "Vorgeschlagen ist ein neuer Ordner. Die laufende Instanz zu überschreiben "
|
||||
+ "wird nicht empfohlen: Sie hält Dateien geöffnet und schreibt Teile davon "
|
||||
+ "sofort wieder zurück."
|
||||
};
|
||||
layout.Controls.Add(targetHint, 1, 2);
|
||||
layout.SetColumnSpan(targetHint, 2);
|
||||
|
||||
// ─── Passphrase ───
|
||||
var needsPassphrase = manifest.HasSecrets;
|
||||
|
||||
var lblPass = new Label
|
||||
{
|
||||
Anchor = AnchorStyles.Left,
|
||||
AutoSize = true,
|
||||
Enabled = needsPassphrase,
|
||||
Text = "Passphrase:"
|
||||
};
|
||||
layout.Controls.Add(lblPass, 0, 3);
|
||||
|
||||
_txtPassphrase.Anchor = AnchorStyles.Left | AnchorStyles.Right;
|
||||
_txtPassphrase.Enabled = needsPassphrase;
|
||||
layout.Controls.Add(_txtPassphrase, 1, 3);
|
||||
|
||||
var passHint = new Label
|
||||
{
|
||||
AutoSize = true,
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Margin = new Padding(0, 2, 0, 10),
|
||||
MaximumSize = new Size(620, 0),
|
||||
Text = needsPassphrase
|
||||
? "Diese Sicherung enthält geschützte Zugangsdaten."
|
||||
: "Diese Sicherung enthält keine Zugangsdaten — API-Schlüssel und Passwörter "
|
||||
+ "müssen nach dem Wiederherstellen neu eingetragen werden."
|
||||
};
|
||||
layout.Controls.Add(passHint, 1, 4);
|
||||
layout.SetColumnSpan(passHint, 2);
|
||||
|
||||
// ─── Überschreiben ───
|
||||
_chkOverwrite.AutoSize = true;
|
||||
_chkOverwrite.Margin = new Padding(0, 0, 0, 12);
|
||||
_chkOverwrite.Text = "Vorhandene Dateien im Zielordner überschreiben";
|
||||
layout.Controls.Add(_chkOverwrite, 1, 5);
|
||||
layout.SetColumnSpan(_chkOverwrite, 2);
|
||||
|
||||
// ─── Schaltflächen ───
|
||||
var buttons = new FlowLayoutPanel
|
||||
{
|
||||
AutoSize = true,
|
||||
Dock = DockStyle.Fill,
|
||||
FlowDirection = FlowDirection.RightToLeft
|
||||
};
|
||||
|
||||
var cancel = new Button { AutoSize = true, DialogResult = DialogResult.Cancel, Text = "Abbrechen" };
|
||||
var ok = new Button { AutoSize = true, Text = "Wiederherstellen" };
|
||||
ok.Click += OnConfirm;
|
||||
|
||||
buttons.Controls.Add(cancel);
|
||||
buttons.Controls.Add(ok);
|
||||
layout.Controls.Add(buttons, 1, 6);
|
||||
layout.SetColumnSpan(buttons, 2);
|
||||
|
||||
Controls.Add(layout);
|
||||
AcceptButton = ok;
|
||||
CancelButton = cancel;
|
||||
}
|
||||
|
||||
private static string DescribeSecrets(BackupManifest manifest) => manifest.HasSecrets
|
||||
? $"{manifest.SecretCount}, mit Passphrase geschützt"
|
||||
: manifest.SecretCount > 0
|
||||
? $"{manifest.SecretCount} entfernt"
|
||||
: "keine";
|
||||
|
||||
private void OnBrowse(object? sender, EventArgs e)
|
||||
{
|
||||
using var dialog = new FolderBrowserDialog
|
||||
{
|
||||
Description = "Zielordner für die Wiederherstellung",
|
||||
UseDescriptionForTitle = true
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog(this) == DialogResult.OK)
|
||||
_txtTarget.Text = dialog.SelectedPath;
|
||||
}
|
||||
|
||||
private void OnConfirm(object? sender, EventArgs e)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(TargetDirectory))
|
||||
{
|
||||
MessageBox.Show(this, "Bitte einen Zielordner angeben.",
|
||||
"Wiederherstellen", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_txtPassphrase.Enabled && string.IsNullOrEmpty(_txtPassphrase.Text))
|
||||
{
|
||||
MessageBox.Show(this,
|
||||
"Diese Sicherung enthält geschützte Zugangsdaten. Ohne Passphrase lässt sie " +
|
||||
"sich nicht wiederherstellen.",
|
||||
"Wiederherstellen", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
_txtPassphrase.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
Generated
+85
-365
@@ -52,6 +52,8 @@ namespace ClawdDotNet
|
||||
label_openRouterStatus = new ToolStripStatusLabel();
|
||||
toolStripStatusLabel1 = new ToolStripStatusLabel();
|
||||
label_openRouterCredits = new ToolStripStatusLabel();
|
||||
toolStripStatusLabel2 = new ToolStripStatusLabel();
|
||||
label_openRouterCreditBalance = new ToolStripStatusLabel();
|
||||
tabControl1 = new TabControl();
|
||||
tabPage_Chat = new TabPage();
|
||||
webView_chat = new Microsoft.Web.WebView2.WinForms.WebView2();
|
||||
@@ -75,12 +77,6 @@ namespace ClawdDotNet
|
||||
splitContainer2 = new SplitContainer();
|
||||
splitContainer1 = new SplitContainer();
|
||||
dgv_agentlist = new DataGridView();
|
||||
col_AgentDisplayName = new DataGridViewTextBoxColumn();
|
||||
col_AgentModel = new DataGridViewTextBoxColumn();
|
||||
col_AgentToolCount = new DataGridViewTextBoxColumn();
|
||||
col_AgentHasIdentity = new DataGridViewTextBoxColumn();
|
||||
col_AgentHasSoul = new DataGridViewTextBoxColumn();
|
||||
col_AgentStatus = new DataGridViewTextBoxColumn();
|
||||
toolStrip_agentSettings = new ToolStrip();
|
||||
btn_addAgent = new ToolStripButton();
|
||||
btn_removeAgent = new ToolStripButton();
|
||||
@@ -94,24 +90,11 @@ namespace ClawdDotNet
|
||||
pg_agentsettings = new PropertyGrid();
|
||||
splitContainer3 = new SplitContainer();
|
||||
dgv_agentTools = new DataGridView();
|
||||
col_ToolAssigned2 = new DataGridViewCheckBoxColumn();
|
||||
col_ToolName2 = new DataGridViewTextBoxColumn();
|
||||
col_ToolDescription2 = new DataGridViewTextBoxColumn();
|
||||
pg_agentToolSettings = new PropertyGrid();
|
||||
tabPage_Worker = new TabPage();
|
||||
tabControl3 = new TabControl();
|
||||
tabPage_jobs = new TabPage();
|
||||
dgv_Jobs = new DataGridView();
|
||||
col_JobType = new DataGridViewTextBoxColumn();
|
||||
col_JobAgentName = new DataGridViewTextBoxColumn();
|
||||
col_JobToolName = new DataGridViewTextBoxColumn();
|
||||
col_JobCron = new DataGridViewTextBoxColumn();
|
||||
col_JobTask = new DataGridViewTextBoxColumn();
|
||||
col_JobNextRun = new DataGridViewTextBoxColumn();
|
||||
col_JobLastRun = new DataGridViewTextBoxColumn();
|
||||
col_JobLastStatus = new DataGridViewTextBoxColumn();
|
||||
col_JobAutostart = new DataGridViewCheckBoxColumn();
|
||||
col_JobStatus = new DataGridViewTextBoxColumn();
|
||||
toolStrip_jobs = new ToolStrip();
|
||||
btn_addJob = new ToolStripButton();
|
||||
btn_editJob = new ToolStripButton();
|
||||
@@ -120,32 +103,19 @@ namespace ClawdDotNet
|
||||
btn_refreshJobs = new ToolStripButton();
|
||||
tabPage_services = new TabPage();
|
||||
dgv_services = new DataGridView();
|
||||
col_SvcName = new DataGridViewTextBoxColumn();
|
||||
col_SvcType = new DataGridViewTextBoxColumn();
|
||||
col_SvcPort = new DataGridViewTextBoxColumn();
|
||||
col_SvcStatus = new DataGridViewTextBoxColumn();
|
||||
col_SvcStartedAt = new DataGridViewTextBoxColumn();
|
||||
col_SvcDescription = new DataGridViewTextBoxColumn();
|
||||
col_SvcBuiltIn = new DataGridViewCheckBoxColumn();
|
||||
toolStrip_services = new ToolStrip();
|
||||
btn_addService = new ToolStripButton();
|
||||
btn_removeService = new ToolStripButton();
|
||||
toolStripSeparator6 = new ToolStripSeparator();
|
||||
btn_startService = new ToolStripButton();
|
||||
btn_stopService = new ToolStripButton();
|
||||
tabPage_jobHistory = new TabPage();
|
||||
dgv_jobhistory = new DataGridView();
|
||||
tabPage_Info = new TabPage();
|
||||
label2 = new Label();
|
||||
label_Softwareinfo = new Label();
|
||||
tabPage_jobHistory = new TabPage();
|
||||
dgv_jobhistory = new DataGridView();
|
||||
col_HistJobName = new DataGridViewTextBoxColumn();
|
||||
col_HistAgent = new DataGridViewTextBoxColumn();
|
||||
col_HistTime = new DataGridViewTextBoxColumn();
|
||||
col_HistDescription = new DataGridViewTextBoxColumn();
|
||||
col_HistInfo = new DataGridViewTextBoxColumn();
|
||||
col_HistStatus = new DataGridViewTextBoxColumn();
|
||||
toolStripStatusLabel2 = new ToolStripStatusLabel();
|
||||
label_openRouterCreditBalance = new ToolStripStatusLabel();
|
||||
tabPage_backup = new TabPage();
|
||||
backupPanel = new ClawdDotNet.UI.BackupPanel();
|
||||
menuStrip1.SuspendLayout();
|
||||
toolStrip1.SuspendLayout();
|
||||
statusStrip1.SuspendLayout();
|
||||
@@ -182,9 +152,9 @@ namespace ClawdDotNet
|
||||
tabPage_services.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)dgv_services).BeginInit();
|
||||
toolStrip_services.SuspendLayout();
|
||||
tabPage_Info.SuspendLayout();
|
||||
tabPage_jobHistory.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)dgv_jobhistory).BeginInit();
|
||||
tabPage_Info.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// menuStrip1
|
||||
@@ -283,6 +253,18 @@ namespace ClawdDotNet
|
||||
label_openRouterCredits.Size = new Size(97, 25);
|
||||
label_openRouterCredits.Text = "Credits: ---";
|
||||
//
|
||||
// toolStripStatusLabel2
|
||||
//
|
||||
toolStripStatusLabel2.Name = "toolStripStatusLabel2";
|
||||
toolStripStatusLabel2.Size = new Size(16, 25);
|
||||
toolStripStatusLabel2.Text = "|";
|
||||
//
|
||||
// label_openRouterCreditBalance
|
||||
//
|
||||
label_openRouterCreditBalance.Name = "label_openRouterCreditBalance";
|
||||
label_openRouterCreditBalance.Size = new Size(179, 25);
|
||||
label_openRouterCreditBalance.Text = "toolStripStatusLabel3";
|
||||
//
|
||||
// tabControl1
|
||||
//
|
||||
tabControl1.Controls.Add(tabPage_Chat);
|
||||
@@ -405,6 +387,7 @@ namespace ClawdDotNet
|
||||
//
|
||||
tabControl2.Controls.Add(tabPage_appsettings);
|
||||
tabControl2.Controls.Add(tabPage_instancesettings);
|
||||
tabControl2.Controls.Add(tabPage_backup);
|
||||
tabControl2.Dock = DockStyle.Fill;
|
||||
tabControl2.Location = new Point(3, 3);
|
||||
tabControl2.Name = "tabControl2";
|
||||
@@ -507,7 +490,6 @@ namespace ClawdDotNet
|
||||
//
|
||||
dgv_agentlist.AllowUserToAddRows = false;
|
||||
dgv_agentlist.AllowUserToDeleteRows = false;
|
||||
dgv_agentlist.AutoGenerateColumns = false;
|
||||
dgv_agentlist.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
|
||||
dgv_agentlist.BackgroundColor = Color.FromArgb(45, 45, 45);
|
||||
dataGridViewCellStyle1.Alignment = DataGridViewContentAlignment.MiddleLeft;
|
||||
@@ -519,7 +501,6 @@ namespace ClawdDotNet
|
||||
dataGridViewCellStyle1.WrapMode = DataGridViewTriState.True;
|
||||
dgv_agentlist.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1;
|
||||
dgv_agentlist.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
dgv_agentlist.Columns.AddRange(new DataGridViewColumn[] { col_AgentDisplayName, col_AgentModel, col_AgentToolCount, col_AgentHasIdentity, col_AgentHasSoul, col_AgentStatus });
|
||||
dataGridViewCellStyle2.Alignment = DataGridViewContentAlignment.MiddleLeft;
|
||||
dataGridViewCellStyle2.BackColor = Color.FromArgb(55, 55, 55);
|
||||
dataGridViewCellStyle2.Font = new Font("Segoe UI", 9F);
|
||||
@@ -539,55 +520,6 @@ namespace ClawdDotNet
|
||||
dgv_agentlist.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
dgv_agentlist.Size = new Size(1519, 505);
|
||||
dgv_agentlist.TabIndex = 0;
|
||||
//
|
||||
// col_AgentDisplayName
|
||||
//
|
||||
col_AgentDisplayName.DataPropertyName = "DisplayName";
|
||||
col_AgentDisplayName.HeaderText = "Name";
|
||||
col_AgentDisplayName.MinimumWidth = 8;
|
||||
col_AgentDisplayName.Name = "col_AgentDisplayName";
|
||||
col_AgentDisplayName.ReadOnly = true;
|
||||
//
|
||||
// col_AgentModel
|
||||
//
|
||||
col_AgentModel.DataPropertyName = "Model";
|
||||
col_AgentModel.HeaderText = "Modell";
|
||||
col_AgentModel.MinimumWidth = 8;
|
||||
col_AgentModel.Name = "col_AgentModel";
|
||||
col_AgentModel.ReadOnly = true;
|
||||
//
|
||||
// col_AgentToolCount
|
||||
//
|
||||
col_AgentToolCount.DataPropertyName = "ToolCount";
|
||||
col_AgentToolCount.HeaderText = "Tools";
|
||||
col_AgentToolCount.MinimumWidth = 8;
|
||||
col_AgentToolCount.Name = "col_AgentToolCount";
|
||||
col_AgentToolCount.ReadOnly = true;
|
||||
//
|
||||
// col_AgentHasIdentity
|
||||
//
|
||||
col_AgentHasIdentity.DataPropertyName = "HasIdentity";
|
||||
col_AgentHasIdentity.HeaderText = "Identity";
|
||||
col_AgentHasIdentity.MinimumWidth = 8;
|
||||
col_AgentHasIdentity.Name = "col_AgentHasIdentity";
|
||||
col_AgentHasIdentity.ReadOnly = true;
|
||||
//
|
||||
// col_AgentHasSoul
|
||||
//
|
||||
col_AgentHasSoul.DataPropertyName = "HasSoul";
|
||||
col_AgentHasSoul.HeaderText = "Soul";
|
||||
col_AgentHasSoul.MinimumWidth = 8;
|
||||
col_AgentHasSoul.Name = "col_AgentHasSoul";
|
||||
col_AgentHasSoul.ReadOnly = true;
|
||||
//
|
||||
// col_AgentStatus
|
||||
//
|
||||
col_AgentStatus.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
||||
col_AgentStatus.DataPropertyName = "Status";
|
||||
col_AgentStatus.HeaderText = "Status";
|
||||
col_AgentStatus.MinimumWidth = 8;
|
||||
col_AgentStatus.Name = "col_AgentStatus";
|
||||
col_AgentStatus.ReadOnly = true;
|
||||
//
|
||||
// toolStrip_agentSettings
|
||||
//
|
||||
@@ -687,7 +619,6 @@ namespace ClawdDotNet
|
||||
//
|
||||
dgv_agentTools.AllowUserToAddRows = false;
|
||||
dgv_agentTools.AllowUserToDeleteRows = false;
|
||||
dgv_agentTools.AutoGenerateColumns = false;
|
||||
dgv_agentTools.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
|
||||
dgv_agentTools.BackgroundColor = Color.FromArgb(45, 45, 45);
|
||||
dataGridViewCellStyle3.Alignment = DataGridViewContentAlignment.MiddleLeft;
|
||||
@@ -699,7 +630,6 @@ namespace ClawdDotNet
|
||||
dataGridViewCellStyle3.WrapMode = DataGridViewTriState.True;
|
||||
dgv_agentTools.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle3;
|
||||
dgv_agentTools.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
dgv_agentTools.Columns.AddRange(new DataGridViewColumn[] { col_ToolAssigned2, col_ToolName2, col_ToolDescription2 });
|
||||
dataGridViewCellStyle4.Alignment = DataGridViewContentAlignment.MiddleLeft;
|
||||
dataGridViewCellStyle4.BackColor = Color.FromArgb(55, 55, 55);
|
||||
dataGridViewCellStyle4.Font = new Font("Segoe UI", 9F);
|
||||
@@ -718,31 +648,6 @@ namespace ClawdDotNet
|
||||
dgv_agentTools.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
dgv_agentTools.Size = new Size(984, 334);
|
||||
dgv_agentTools.TabIndex = 0;
|
||||
//
|
||||
// col_ToolAssigned2
|
||||
//
|
||||
col_ToolAssigned2.DataPropertyName = "Assigned";
|
||||
col_ToolAssigned2.HeaderText = "Aktiv";
|
||||
col_ToolAssigned2.MinimumWidth = 8;
|
||||
col_ToolAssigned2.Name = "col_ToolAssigned2";
|
||||
col_ToolAssigned2.Width = 60;
|
||||
//
|
||||
// col_ToolName2
|
||||
//
|
||||
col_ToolName2.DataPropertyName = "ToolName";
|
||||
col_ToolName2.HeaderText = "Tool";
|
||||
col_ToolName2.MinimumWidth = 8;
|
||||
col_ToolName2.Name = "col_ToolName2";
|
||||
col_ToolName2.ReadOnly = true;
|
||||
//
|
||||
// col_ToolDescription2
|
||||
//
|
||||
col_ToolDescription2.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
||||
col_ToolDescription2.DataPropertyName = "Description";
|
||||
col_ToolDescription2.HeaderText = "Beschreibung";
|
||||
col_ToolDescription2.MinimumWidth = 8;
|
||||
col_ToolDescription2.Name = "col_ToolDescription2";
|
||||
col_ToolDescription2.ReadOnly = true;
|
||||
//
|
||||
// pg_agentToolSettings
|
||||
//
|
||||
@@ -792,7 +697,6 @@ namespace ClawdDotNet
|
||||
//
|
||||
dgv_Jobs.AllowUserToAddRows = false;
|
||||
dgv_Jobs.AllowUserToDeleteRows = false;
|
||||
dgv_Jobs.AutoGenerateColumns = false;
|
||||
dgv_Jobs.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
|
||||
dgv_Jobs.BackgroundColor = Color.FromArgb(45, 45, 45);
|
||||
dataGridViewCellStyle5.Alignment = DataGridViewContentAlignment.MiddleLeft;
|
||||
@@ -804,7 +708,6 @@ namespace ClawdDotNet
|
||||
dataGridViewCellStyle5.WrapMode = DataGridViewTriState.True;
|
||||
dgv_Jobs.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle5;
|
||||
dgv_Jobs.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
dgv_Jobs.Columns.AddRange(new DataGridViewColumn[] { col_JobType, col_JobAgentName, col_JobToolName, col_JobCron, col_JobTask, col_JobNextRun, col_JobLastRun, col_JobLastStatus, col_JobAutostart, col_JobStatus });
|
||||
dataGridViewCellStyle6.Alignment = DataGridViewContentAlignment.MiddleLeft;
|
||||
dataGridViewCellStyle6.BackColor = Color.FromArgb(55, 55, 55);
|
||||
dataGridViewCellStyle6.Font = new Font("Segoe UI", 9F);
|
||||
@@ -825,87 +728,6 @@ namespace ClawdDotNet
|
||||
dgv_Jobs.Size = new Size(1870, 803);
|
||||
dgv_Jobs.TabIndex = 1;
|
||||
dgv_Jobs.CellContentClick += dgv_Jobs_CellContentClick;
|
||||
//
|
||||
// col_JobType
|
||||
//
|
||||
col_JobType.DataPropertyName = "JobType";
|
||||
col_JobType.HeaderText = "Typ";
|
||||
col_JobType.MinimumWidth = 8;
|
||||
col_JobType.Name = "col_JobType";
|
||||
col_JobType.ReadOnly = true;
|
||||
//
|
||||
// col_JobAgentName
|
||||
//
|
||||
col_JobAgentName.DataPropertyName = "AgentName";
|
||||
col_JobAgentName.HeaderText = "Agent";
|
||||
col_JobAgentName.MinimumWidth = 8;
|
||||
col_JobAgentName.Name = "col_JobAgentName";
|
||||
col_JobAgentName.ReadOnly = true;
|
||||
//
|
||||
// col_JobToolName
|
||||
//
|
||||
col_JobToolName.DataPropertyName = "ToolName";
|
||||
col_JobToolName.HeaderText = "Tool";
|
||||
col_JobToolName.MinimumWidth = 8;
|
||||
col_JobToolName.Name = "col_JobToolName";
|
||||
col_JobToolName.ReadOnly = true;
|
||||
//
|
||||
// col_JobCron
|
||||
//
|
||||
col_JobCron.DataPropertyName = "CronExpression";
|
||||
col_JobCron.HeaderText = "Zeitplan";
|
||||
col_JobCron.MinimumWidth = 8;
|
||||
col_JobCron.Name = "col_JobCron";
|
||||
col_JobCron.ReadOnly = true;
|
||||
//
|
||||
// col_JobTask
|
||||
//
|
||||
col_JobTask.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
||||
col_JobTask.DataPropertyName = "TaskMessage";
|
||||
col_JobTask.HeaderText = "Aufgabe";
|
||||
col_JobTask.MinimumWidth = 8;
|
||||
col_JobTask.Name = "col_JobTask";
|
||||
col_JobTask.ReadOnly = true;
|
||||
//
|
||||
// col_JobNextRun
|
||||
//
|
||||
col_JobNextRun.DataPropertyName = "NextRun";
|
||||
col_JobNextRun.HeaderText = "Naechste Ausf.";
|
||||
col_JobNextRun.MinimumWidth = 8;
|
||||
col_JobNextRun.Name = "col_JobNextRun";
|
||||
col_JobNextRun.ReadOnly = true;
|
||||
//
|
||||
// col_JobLastRun
|
||||
//
|
||||
col_JobLastRun.DataPropertyName = "LastRun";
|
||||
col_JobLastRun.HeaderText = "Letzte Ausf.";
|
||||
col_JobLastRun.MinimumWidth = 8;
|
||||
col_JobLastRun.Name = "col_JobLastRun";
|
||||
col_JobLastRun.ReadOnly = true;
|
||||
//
|
||||
// col_JobLastStatus
|
||||
//
|
||||
col_JobLastStatus.DataPropertyName = "LastStatus";
|
||||
col_JobLastStatus.HeaderText = "Letzter Status";
|
||||
col_JobLastStatus.MinimumWidth = 8;
|
||||
col_JobLastStatus.Name = "col_JobLastStatus";
|
||||
col_JobLastStatus.ReadOnly = true;
|
||||
//
|
||||
// col_JobAutostart
|
||||
//
|
||||
col_JobAutostart.DataPropertyName = "RunOnStart";
|
||||
col_JobAutostart.HeaderText = "Autostart";
|
||||
col_JobAutostart.MinimumWidth = 8;
|
||||
col_JobAutostart.Name = "col_JobAutostart";
|
||||
col_JobAutostart.ReadOnly = true;
|
||||
//
|
||||
// col_JobStatus
|
||||
//
|
||||
col_JobStatus.DataPropertyName = "Status";
|
||||
col_JobStatus.HeaderText = "Status";
|
||||
col_JobStatus.MinimumWidth = 8;
|
||||
col_JobStatus.Name = "col_JobStatus";
|
||||
col_JobStatus.ReadOnly = true;
|
||||
//
|
||||
// toolStrip_jobs
|
||||
//
|
||||
@@ -967,7 +789,6 @@ namespace ClawdDotNet
|
||||
//
|
||||
dgv_services.AllowUserToAddRows = false;
|
||||
dgv_services.AllowUserToDeleteRows = false;
|
||||
dgv_services.AutoGenerateColumns = false;
|
||||
dgv_services.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
|
||||
dgv_services.BackgroundColor = Color.FromArgb(45, 45, 45);
|
||||
dataGridViewCellStyle7.Alignment = DataGridViewContentAlignment.MiddleLeft;
|
||||
@@ -979,7 +800,6 @@ namespace ClawdDotNet
|
||||
dataGridViewCellStyle7.WrapMode = DataGridViewTriState.True;
|
||||
dgv_services.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle7;
|
||||
dgv_services.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
dgv_services.Columns.AddRange(new DataGridViewColumn[] { col_SvcName, col_SvcType, col_SvcPort, col_SvcStatus, col_SvcStartedAt, col_SvcDescription, col_SvcBuiltIn });
|
||||
dataGridViewCellStyle8.Alignment = DataGridViewContentAlignment.MiddleLeft;
|
||||
dataGridViewCellStyle8.BackColor = Color.FromArgb(55, 55, 55);
|
||||
dataGridViewCellStyle8.Font = new Font("Segoe UI", 9F);
|
||||
@@ -999,63 +819,6 @@ namespace ClawdDotNet
|
||||
dgv_services.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
dgv_services.Size = new Size(1870, 803);
|
||||
dgv_services.TabIndex = 1;
|
||||
//
|
||||
// col_SvcName
|
||||
//
|
||||
col_SvcName.DataPropertyName = "Name";
|
||||
col_SvcName.HeaderText = "Name";
|
||||
col_SvcName.MinimumWidth = 8;
|
||||
col_SvcName.Name = "col_SvcName";
|
||||
col_SvcName.ReadOnly = true;
|
||||
//
|
||||
// col_SvcType
|
||||
//
|
||||
col_SvcType.DataPropertyName = "Type";
|
||||
col_SvcType.HeaderText = "Typ";
|
||||
col_SvcType.MinimumWidth = 8;
|
||||
col_SvcType.Name = "col_SvcType";
|
||||
col_SvcType.ReadOnly = true;
|
||||
//
|
||||
// col_SvcPort
|
||||
//
|
||||
col_SvcPort.DataPropertyName = "Port";
|
||||
col_SvcPort.HeaderText = "Port";
|
||||
col_SvcPort.MinimumWidth = 8;
|
||||
col_SvcPort.Name = "col_SvcPort";
|
||||
col_SvcPort.ReadOnly = true;
|
||||
//
|
||||
// col_SvcStatus
|
||||
//
|
||||
col_SvcStatus.DataPropertyName = "Status";
|
||||
col_SvcStatus.HeaderText = "Status";
|
||||
col_SvcStatus.MinimumWidth = 8;
|
||||
col_SvcStatus.Name = "col_SvcStatus";
|
||||
col_SvcStatus.ReadOnly = true;
|
||||
//
|
||||
// col_SvcStartedAt
|
||||
//
|
||||
col_SvcStartedAt.DataPropertyName = "StartedAt";
|
||||
col_SvcStartedAt.HeaderText = "Gestartet";
|
||||
col_SvcStartedAt.MinimumWidth = 8;
|
||||
col_SvcStartedAt.Name = "col_SvcStartedAt";
|
||||
col_SvcStartedAt.ReadOnly = true;
|
||||
//
|
||||
// col_SvcDescription
|
||||
//
|
||||
col_SvcDescription.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
||||
col_SvcDescription.DataPropertyName = "Description";
|
||||
col_SvcDescription.HeaderText = "Beschreibung";
|
||||
col_SvcDescription.MinimumWidth = 8;
|
||||
col_SvcDescription.Name = "col_SvcDescription";
|
||||
col_SvcDescription.ReadOnly = true;
|
||||
//
|
||||
// col_SvcBuiltIn
|
||||
//
|
||||
col_SvcBuiltIn.DataPropertyName = "BuiltIn";
|
||||
col_SvcBuiltIn.HeaderText = "Integriert";
|
||||
col_SvcBuiltIn.MinimumWidth = 8;
|
||||
col_SvcBuiltIn.Name = "col_SvcBuiltIn";
|
||||
col_SvcBuiltIn.ReadOnly = true;
|
||||
//
|
||||
// toolStrip_services
|
||||
//
|
||||
@@ -1099,6 +862,52 @@ namespace ClawdDotNet
|
||||
btn_stopService.Size = new Size(83, 29);
|
||||
btn_stopService.Text = "Stoppen";
|
||||
//
|
||||
// tabPage_jobHistory
|
||||
//
|
||||
tabPage_jobHistory.Controls.Add(dgv_jobhistory);
|
||||
tabPage_jobHistory.Location = new Point(4, 34);
|
||||
tabPage_jobHistory.Name = "tabPage_jobHistory";
|
||||
tabPage_jobHistory.Padding = new Padding(3);
|
||||
tabPage_jobHistory.Size = new Size(1876, 843);
|
||||
tabPage_jobHistory.TabIndex = 2;
|
||||
tabPage_jobHistory.Text = "Job History";
|
||||
tabPage_jobHistory.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// dgv_jobhistory
|
||||
//
|
||||
dgv_jobhistory.AllowUserToAddRows = false;
|
||||
dgv_jobhistory.AllowUserToDeleteRows = false;
|
||||
dgv_jobhistory.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
|
||||
dgv_jobhistory.BackgroundColor = Color.FromArgb(45, 45, 45);
|
||||
dataGridViewCellStyle9.Alignment = DataGridViewContentAlignment.MiddleLeft;
|
||||
dataGridViewCellStyle9.BackColor = Color.FromArgb(35, 35, 35);
|
||||
dataGridViewCellStyle9.Font = new Font("Segoe UI", 9F);
|
||||
dataGridViewCellStyle9.ForeColor = Color.White;
|
||||
dataGridViewCellStyle9.SelectionBackColor = SystemColors.Highlight;
|
||||
dataGridViewCellStyle9.SelectionForeColor = SystemColors.HighlightText;
|
||||
dataGridViewCellStyle9.WrapMode = DataGridViewTriState.True;
|
||||
dgv_jobhistory.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle9;
|
||||
dgv_jobhistory.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
dataGridViewCellStyle10.Alignment = DataGridViewContentAlignment.MiddleLeft;
|
||||
dataGridViewCellStyle10.BackColor = Color.FromArgb(55, 55, 55);
|
||||
dataGridViewCellStyle10.Font = new Font("Segoe UI", 9F);
|
||||
dataGridViewCellStyle10.ForeColor = Color.White;
|
||||
dataGridViewCellStyle10.SelectionBackColor = Color.FromArgb(80, 120, 200);
|
||||
dataGridViewCellStyle10.SelectionForeColor = SystemColors.HighlightText;
|
||||
dataGridViewCellStyle10.WrapMode = DataGridViewTriState.False;
|
||||
dgv_jobhistory.DefaultCellStyle = dataGridViewCellStyle10;
|
||||
dgv_jobhistory.Dock = DockStyle.Fill;
|
||||
dgv_jobhistory.EnableHeadersVisualStyles = false;
|
||||
dgv_jobhistory.Location = new Point(3, 3);
|
||||
dgv_jobhistory.MultiSelect = false;
|
||||
dgv_jobhistory.Name = "dgv_jobhistory";
|
||||
dgv_jobhistory.ReadOnly = true;
|
||||
dgv_jobhistory.RowHeadersVisible = false;
|
||||
dgv_jobhistory.RowHeadersWidth = 62;
|
||||
dgv_jobhistory.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
dgv_jobhistory.Size = new Size(1870, 837);
|
||||
dgv_jobhistory.TabIndex = 0;
|
||||
//
|
||||
// tabPage_Info
|
||||
//
|
||||
tabPage_Info.Controls.Add(label2);
|
||||
@@ -1129,115 +938,24 @@ namespace ClawdDotNet
|
||||
label_Softwareinfo.TabIndex = 0;
|
||||
label_Softwareinfo.Text = "label1";
|
||||
//
|
||||
// tabPage_jobHistory
|
||||
// tabPage_backup
|
||||
//
|
||||
tabPage_jobHistory.Controls.Add(dgv_jobhistory);
|
||||
tabPage_jobHistory.Location = new Point(4, 34);
|
||||
tabPage_jobHistory.Name = "tabPage_jobHistory";
|
||||
tabPage_jobHistory.Padding = new Padding(3);
|
||||
tabPage_jobHistory.Size = new Size(1876, 843);
|
||||
tabPage_jobHistory.TabIndex = 2;
|
||||
tabPage_jobHistory.Text = "Job History";
|
||||
tabPage_jobHistory.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// dgv_jobhistory
|
||||
//
|
||||
dgv_jobhistory.AllowUserToAddRows = false;
|
||||
dgv_jobhistory.AllowUserToDeleteRows = false;
|
||||
dgv_jobhistory.AutoGenerateColumns = false;
|
||||
dgv_jobhistory.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
|
||||
dgv_jobhistory.BackgroundColor = Color.FromArgb(45, 45, 45);
|
||||
dataGridViewCellStyle9.Alignment = DataGridViewContentAlignment.MiddleLeft;
|
||||
dataGridViewCellStyle9.BackColor = Color.FromArgb(35, 35, 35);
|
||||
dataGridViewCellStyle9.Font = new Font("Segoe UI", 9F);
|
||||
dataGridViewCellStyle9.ForeColor = Color.White;
|
||||
dataGridViewCellStyle9.SelectionBackColor = SystemColors.Highlight;
|
||||
dataGridViewCellStyle9.SelectionForeColor = SystemColors.HighlightText;
|
||||
dataGridViewCellStyle9.WrapMode = DataGridViewTriState.True;
|
||||
dgv_jobhistory.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle9;
|
||||
dgv_jobhistory.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
dgv_jobhistory.Columns.AddRange(new DataGridViewColumn[] { col_HistJobName, col_HistAgent, col_HistTime, col_HistDescription, col_HistInfo, col_HistStatus });
|
||||
dataGridViewCellStyle10.Alignment = DataGridViewContentAlignment.MiddleLeft;
|
||||
dataGridViewCellStyle10.BackColor = Color.FromArgb(55, 55, 55);
|
||||
dataGridViewCellStyle10.Font = new Font("Segoe UI", 9F);
|
||||
dataGridViewCellStyle10.ForeColor = Color.White;
|
||||
dataGridViewCellStyle10.SelectionBackColor = Color.FromArgb(80, 120, 200);
|
||||
dataGridViewCellStyle10.SelectionForeColor = SystemColors.HighlightText;
|
||||
dataGridViewCellStyle10.WrapMode = DataGridViewTriState.False;
|
||||
dgv_jobhistory.DefaultCellStyle = dataGridViewCellStyle10;
|
||||
dgv_jobhistory.Dock = DockStyle.Fill;
|
||||
dgv_jobhistory.EnableHeadersVisualStyles = false;
|
||||
dgv_jobhistory.Location = new Point(3, 3);
|
||||
dgv_jobhistory.MultiSelect = false;
|
||||
dgv_jobhistory.Name = "dgv_jobhistory";
|
||||
dgv_jobhistory.ReadOnly = true;
|
||||
dgv_jobhistory.RowHeadersVisible = false;
|
||||
dgv_jobhistory.RowHeadersWidth = 62;
|
||||
dgv_jobhistory.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
dgv_jobhistory.Size = new Size(1870, 837);
|
||||
dgv_jobhistory.TabIndex = 0;
|
||||
tabPage_backup.Controls.Add(backupPanel);
|
||||
tabPage_backup.Location = new Point(4, 34);
|
||||
tabPage_backup.Name = "tabPage_backup";
|
||||
tabPage_backup.Padding = new Padding(3);
|
||||
tabPage_backup.Size = new Size(1876, 843);
|
||||
tabPage_backup.TabIndex = 2;
|
||||
tabPage_backup.Text = "Backup";
|
||||
tabPage_backup.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// col_HistJobName
|
||||
// backupPanel
|
||||
//
|
||||
col_HistJobName.DataPropertyName = "JobName";
|
||||
col_HistJobName.HeaderText = "Job";
|
||||
col_HistJobName.MinimumWidth = 8;
|
||||
col_HistJobName.Name = "col_HistJobName";
|
||||
col_HistJobName.ReadOnly = true;
|
||||
//
|
||||
// col_HistAgent
|
||||
//
|
||||
col_HistAgent.DataPropertyName = "Agent";
|
||||
col_HistAgent.HeaderText = "Agent";
|
||||
col_HistAgent.MinimumWidth = 8;
|
||||
col_HistAgent.Name = "col_HistAgent";
|
||||
col_HistAgent.ReadOnly = true;
|
||||
//
|
||||
// col_HistTime
|
||||
//
|
||||
col_HistTime.DataPropertyName = "Time";
|
||||
col_HistTime.HeaderText = "Zeit";
|
||||
col_HistTime.MinimumWidth = 8;
|
||||
col_HistTime.Name = "col_HistTime";
|
||||
col_HistTime.ReadOnly = true;
|
||||
col_HistTime.DefaultCellStyle = new DataGridViewCellStyle { Format = "yyyy-MM-dd HH:mm:ss", BackColor = Color.FromArgb(55, 55, 55), ForeColor = Color.White, SelectionBackColor = Color.FromArgb(80, 120, 200), SelectionForeColor = SystemColors.HighlightText };
|
||||
//
|
||||
// col_HistDescription
|
||||
//
|
||||
col_HistDescription.DataPropertyName = "JobDescription";
|
||||
col_HistDescription.HeaderText = "Beschreibung";
|
||||
col_HistDescription.MinimumWidth = 8;
|
||||
col_HistDescription.Name = "col_HistDescription";
|
||||
col_HistDescription.ReadOnly = true;
|
||||
//
|
||||
// col_HistInfo
|
||||
//
|
||||
col_HistInfo.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
||||
col_HistInfo.DataPropertyName = "Info";
|
||||
col_HistInfo.HeaderText = "Info";
|
||||
col_HistInfo.MinimumWidth = 8;
|
||||
col_HistInfo.Name = "col_HistInfo";
|
||||
col_HistInfo.ReadOnly = true;
|
||||
//
|
||||
// col_HistStatus
|
||||
//
|
||||
col_HistStatus.DataPropertyName = "Status";
|
||||
col_HistStatus.HeaderText = "Status";
|
||||
col_HistStatus.MinimumWidth = 8;
|
||||
col_HistStatus.Name = "col_HistStatus";
|
||||
col_HistStatus.ReadOnly = true;
|
||||
//
|
||||
// toolStripStatusLabel2
|
||||
//
|
||||
toolStripStatusLabel2.Name = "toolStripStatusLabel2";
|
||||
toolStripStatusLabel2.Size = new Size(16, 25);
|
||||
toolStripStatusLabel2.Text = "|";
|
||||
//
|
||||
// label_openRouterCreditBalance
|
||||
//
|
||||
label_openRouterCreditBalance.Name = "label_openRouterCreditBalance";
|
||||
label_openRouterCreditBalance.Size = new Size(179, 25);
|
||||
label_openRouterCreditBalance.Text = "toolStripStatusLabel3";
|
||||
backupPanel.Dock = DockStyle.Fill;
|
||||
backupPanel.Location = new Point(3, 3);
|
||||
backupPanel.Name = "backupPanel";
|
||||
backupPanel.Size = new Size(1870, 837);
|
||||
backupPanel.TabIndex = 0;
|
||||
//
|
||||
// frm_main
|
||||
//
|
||||
@@ -1301,10 +1019,10 @@ namespace ClawdDotNet
|
||||
((System.ComponentModel.ISupportInitialize)dgv_services).EndInit();
|
||||
toolStrip_services.ResumeLayout(false);
|
||||
toolStrip_services.PerformLayout();
|
||||
tabPage_Info.ResumeLayout(false);
|
||||
tabPage_Info.PerformLayout();
|
||||
tabPage_jobHistory.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)dgv_jobhistory).EndInit();
|
||||
tabPage_Info.ResumeLayout(false);
|
||||
tabPage_Info.PerformLayout();
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
@@ -1447,5 +1165,7 @@ namespace ClawdDotNet
|
||||
|
||||
private ToolStripStatusLabel toolStripStatusLabel2;
|
||||
private ToolStripStatusLabel label_openRouterCreditBalance;
|
||||
private TabPage tabPage_backup;
|
||||
private ClawdDotNet.UI.BackupPanel backupPanel;
|
||||
}
|
||||
}
|
||||
|
||||
+25
@@ -32,6 +32,7 @@ public partial class frm_main : Form
|
||||
private LiveLogViewerService? _logViewer;
|
||||
private OpenRouterStatusService? _statusService;
|
||||
private JobHistoryService? _jobHistoryService;
|
||||
private BackupScheduler? _backupScheduler;
|
||||
|
||||
// ─── Chat ───
|
||||
private WebViewBridge? _bridge;
|
||||
@@ -107,6 +108,7 @@ public partial class frm_main : Form
|
||||
SetupSettingsTab();
|
||||
SetupAgentSettingsTab();
|
||||
SetupWorkerTab();
|
||||
SetupBackupTab();
|
||||
SetupStatusStrip();
|
||||
SetupBuildInfo();
|
||||
WireEvents();
|
||||
@@ -121,6 +123,28 @@ public partial class frm_main : Form
|
||||
Text = $"ClawdDotNet - {_instanceConfig.InstanceName}";
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════
|
||||
// BACKUP TAB
|
||||
// ═══════════════════════════════════════════════════
|
||||
|
||||
private void SetupBackupTab()
|
||||
{
|
||||
backupPanel.Initialize(
|
||||
_instancePath,
|
||||
_instanceConfig.InstanceName,
|
||||
_settingsManager,
|
||||
_loggerFactory.CreateLogger("ClawdDotNet.Backup"));
|
||||
|
||||
_backupScheduler = new BackupScheduler(
|
||||
_instancePath,
|
||||
_instanceConfig.InstanceName,
|
||||
_settingsManager,
|
||||
_loggerFactory.CreateLogger("ClawdDotNet.Backup.Scheduler"));
|
||||
|
||||
_backupScheduler.OnBackupCreated += _ => BeginInvoke(() => backupPanel.RefreshFromOutside());
|
||||
_backupScheduler.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verdrahtet alle Designer-Controls mit ihren Event-Handlern.
|
||||
/// Die Controls selbst werden im Designer erstellt.
|
||||
@@ -1513,6 +1537,7 @@ public partial class frm_main : Form
|
||||
_bridge?.Dispose();
|
||||
_logViewer?.Dispose();
|
||||
_statusService?.Dispose();
|
||||
_backupScheduler?.Dispose();
|
||||
_logger.LogInformation("MainForm wird geschlossen");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user