Files
ClawdDotNet/src/ClawdDotNet.Desktop/Views/TasksPageView.axaml
T
RichardandClaude Opus 5.5 ad71682c2d i18n: Englisch als Hauptsprache, Sprachpakete fuer Desktop und WebUI (#661)
Umbau durch Gemini Flash, Durchsicht und Nacharbeit durch Claude.

Umfang:
- Desktop: LocalizationManager (JSON-Pakete, Rueckfall auf Englisch,
  Umschalten zur Laufzeit), Localize-Markup-Erweiterung, Sprachwahl in den
  Einstellungen (AppSettings.Language, Vorgabe "en"); 317 Schluessel je Sprache
- WebUI: alle Ansichten auf t()/data-i18n, 656 Schluessel je Sprache
- Agenten-Werkzeuge: Beschreibungen, Schemata und Meldungen auf Englisch
- Tests an die englischen Meldungen angepasst, keine Pruefung abgeschwaecht

Nacharbeit bei der Durchsicht:
- ~25 Kommentarbloecke mit Begruendungen waren ersatzlos entfallen, darunter
  sicherheitsrelevante (NTFS-Datenstrom beim shared:-Praefix, Bremse gegen
  Agentenschleifen, doppelter POST beim Anlegen einer Instanz). Wieder
  eingesetzt: auf Englisch, wo die Datei umgestellt ist, sonst im Wortlaut.
- Drei WebUI-Schluessel fehlten (logs.paused, instances.instanceOfflineError,
  instances.noInstanceActiveError). Weil t() bei fehlendem Schluessel den
  Schluessel selbst liefert, griff der "|| err.message"-Rueckfall nie — die
  Oberflaeche haette den Schluesselnamen angezeigt.

Geprueft: Schluesselgleichheit en/de, Platzhalter je Schluessel, jeder im
Code benutzte Schluessel vorhanden, node --check fuer alle Module,
1.220 Tests gruen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 09:43:51 +02:00

258 lines
14 KiB
XML

<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ClawdDotNet.Desktop.ViewModels"
xmlns:models="using:ClawdDotNet.App.Models"
xmlns:ctrl="using:ClawdDotNet.Desktop.Controls"
xmlns:conv="using:ClawdDotNet.Desktop.Converters"
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="740"
x:Class="ClawdDotNet.Desktop.Views.TasksPageView"
x:DataType="vm:TasksPageViewModel">
<DockPanel>
<!-- Segmentleiste statt Registerkarten. Die Knöpfe überlappen sich um einen
Bildpunkt, damit zwischen ihnen nur eine Linie steht und nicht zwei. -->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal"
HorizontalAlignment="Left" Margin="0,0,0,14">
<ToggleButton Content="{Binding L[desktop.tasks.tabJobs]}" Margin="0,0,-1,0"
IsChecked="{Binding IsJobsTab, Mode=OneWay}"
Command="{Binding SelectTabCommand}" CommandParameter="Jobs" />
<ToggleButton Content="{Binding L[desktop.tasks.tabServices]}" Margin="0,0,-1,0"
IsChecked="{Binding IsServicesTab, Mode=OneWay}"
Command="{Binding SelectTabCommand}" CommandParameter="Services" />
<ToggleButton Content="{Binding L[desktop.tasks.tabHistory]}"
IsChecked="{Binding IsHistoryTab, Mode=OneWay}"
Command="{Binding SelectTabCommand}" CommandParameter="History" />
</StackPanel>
<!-- Statuszeile des Bereichs -->
<TextBlock DockPanel.Dock="Bottom" Classes="caption" Margin="0,10,0,0"
Text="{Binding StatusText}" TextWrapping="Wrap" />
<Panel>
<!-- ══════════════════ Geplante Jobs ══════════════════ -->
<DockPanel IsVisible="{Binding IsJobsTab}">
<WrapPanel DockPanel.Dock="Top" Classes="toolbar">
<Button Classes="toolbar primary" Command="{Binding AddJobCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlus}" Width="14" Height="14" />
<TextBlock Text="{Binding L[desktop.tasks.addJob]}" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding EditJobCommand}"
IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPencil}" Width="14" Height="14" />
<TextBlock Text="{Binding L[desktop.tasks.editJob]}" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding RemoveJobCommand}"
IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconTrash}" Width="14" Height="14" />
<TextBlock Text="{Binding L[desktop.tasks.removeJob]}" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding ToggleJobEnabledCommand}"
IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPower}" Width="14" Height="14" />
<TextBlock Text="{Binding L[desktop.tasks.toggleJob]}" />
</StackPanel>
</Button>
<Border Classes="sep" />
<Button Classes="toolbar" Command="{Binding RunJobNowCommand}"
IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlay}" Width="12" Height="12" IsFilled="True" />
<TextBlock Text="{Binding L[desktop.tasks.runJobNow]}" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding RefreshJobListCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconRefresh}" Width="14" Height="14" />
<TextBlock Text="{Binding L[desktop.tasks.refresh]}" />
</StackPanel>
</Button>
</WrapPanel>
<Border DockPanel.Dock="Top" Classes="thead">
<Grid ColumnDefinitions="1.1*,1.3*,2.2*,1.1*,1.3*,1.3*,0.9*">
<TextBlock Grid.Column="0" Classes="th" Text="{Binding L[desktop.tasks.thType]}" />
<TextBlock Grid.Column="1" Classes="th" Text="{Binding L[desktop.tasks.thAgent]}" />
<TextBlock Grid.Column="2" Classes="th" Text="{Binding L[desktop.tasks.thTask]}" />
<TextBlock Grid.Column="3" Classes="th" Text="{Binding L[desktop.tasks.thCron]}" />
<TextBlock Grid.Column="4" Classes="th" Text="{Binding L[desktop.tasks.thNextRun]}" />
<TextBlock Grid.Column="5" Classes="th" Text="{Binding L[desktop.tasks.thLastRun]}" />
<TextBlock Grid.Column="6" Classes="th" Text="{Binding L[desktop.tasks.thStatus]}" />
</Grid>
</Border>
<ListBox Classes="table" ItemsSource="{Binding JobEntries}"
SelectedItem="{Binding SelectedJob}">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:JobDisplayItemViewModel">
<Border Classes="tr" Padding="7">
<Grid ColumnDefinitions="1.1*,1.3*,2.2*,1.1*,1.3*,1.3*,0.9*">
<TextBlock Grid.Column="0" Classes="td" Text="{Binding JobType}" />
<TextBlock Grid.Column="1" Classes="td" Text="{Binding AgentName}" />
<TextBlock Grid.Column="2" Classes="td" Text="{Binding TaskMessage}" />
<TextBlock Grid.Column="3" Classes="td mono" Text="{Binding CronExpression}" />
<TextBlock Grid.Column="4" Classes="td" Text="{Binding NextRun}" />
<TextBlock Grid.Column="5" Classes="td" Text="{Binding LastRun}" />
<Border Grid.Column="6" Classes="tag"
Classes.ok="{Binding Status,
Converter={x:Static conv:StringEqualsConverter.Instance},
ConverterParameter=Aktiv}">
<TextBlock Text="{Binding Status}" />
</Border>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
<!-- ══════════════════ Dienste ══════════════════ -->
<DockPanel IsVisible="{Binding IsServicesTab}">
<WrapPanel DockPanel.Dock="Top" Classes="toolbar">
<Button Classes="toolbar primary" Command="{Binding AddServiceCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlus}" Width="14" Height="14" />
<TextBlock Text="{Binding L[desktop.services.addService]}" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding RemoveServiceCommand}"
IsEnabled="{Binding SelectedService, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconTrash}" Width="14" Height="14" />
<TextBlock Text="{Binding L[desktop.services.removeService]}" />
</StackPanel>
</Button>
<Border Classes="sep" />
<!-- Der Entwurf zeigt hier „Start"/„Stop". Umgelegt wird aber das Kennzeichen
in der Konfiguration, nicht ein laufender Prozess — deshalb die ehrlichere
Beschriftung. -->
<Button Classes="toolbar" Command="{Binding ToggleServiceEnabledCommand}"
IsEnabled="{Binding SelectedService, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPower}" Width="14" Height="14" />
<TextBlock Text="{Binding L[desktop.services.toggleService]}" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding RefreshServiceListCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconRefresh}" Width="14" Height="14" />
<TextBlock Text="{Binding L[desktop.tasks.refresh]}" />
</StackPanel>
</Button>
</WrapPanel>
<Border DockPanel.Dock="Top" Classes="thead">
<Grid ColumnDefinitions="1.8*,1*,0.7*,1*,3*">
<TextBlock Grid.Column="0" Classes="th" Text="{Binding L[desktop.services.thName]}" />
<TextBlock Grid.Column="1" Classes="th" Text="{Binding L[desktop.services.thType]}" />
<TextBlock Grid.Column="2" Classes="th" Text="{Binding L[desktop.services.thPort]}" />
<TextBlock Grid.Column="3" Classes="th" Text="{Binding L[desktop.services.thStatus]}" />
<TextBlock Grid.Column="4" Classes="th" Text="{Binding L[desktop.services.thDescription]}" />
</Grid>
</Border>
<ListBox Classes="table" ItemsSource="{Binding ServiceEntries}"
SelectedItem="{Binding SelectedService}">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:ServiceDisplayItemViewModel">
<Border Classes="tr" Padding="7">
<Grid ColumnDefinitions="1.8*,1*,0.7*,1*,3*">
<TextBlock Grid.Column="0" Classes="td" FontWeight="SemiBold" Text="{Binding Name}" />
<TextBlock Grid.Column="1" Classes="td" Text="{Binding Type}" />
<TextBlock Grid.Column="2" Classes="td" Text="{Binding Port}" />
<Border Grid.Column="3" Classes="tag"
Classes.ok="{Binding Status,
Converter={x:Static conv:StringEqualsConverter.Instance},
ConverterParameter=Bereit}">
<TextBlock Text="{Binding Status}" />
</Border>
<TextBlock Grid.Column="4" Classes="td dim" Text="{Binding Description}" />
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
<!-- ══════════════════ Verlauf ══════════════════ -->
<DockPanel IsVisible="{Binding IsHistoryTab}">
<StackPanel DockPanel.Dock="Top" Classes="toolbar">
<Button Classes="toolbar" Command="{Binding RefreshJobHistoryCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconRefresh}" Width="14" Height="14" />
<TextBlock Text="{Binding L[desktop.history.refresh]}" />
</StackPanel>
</Button>
</StackPanel>
<Border DockPanel.Dock="Top" Classes="thead">
<Grid ColumnDefinitions="1.3*,1.6*,1.4*,2*,0.9*,2.4*">
<TextBlock Grid.Column="0" Classes="th" Text="{Binding L[desktop.history.thTime]}" />
<TextBlock Grid.Column="1" Classes="th" Text="{Binding L[desktop.history.thJob]}" />
<TextBlock Grid.Column="2" Classes="th" Text="{Binding L[desktop.history.thAgent]}" />
<TextBlock Grid.Column="3" Classes="th" Text="{Binding L[desktop.history.thDescription]}" />
<TextBlock Grid.Column="4" Classes="th" Text="{Binding L[desktop.history.thStatus]}" />
<TextBlock Grid.Column="5" Classes="th" Text="{Binding L[desktop.history.thDetails]}" />
</Grid>
</Border>
<ItemsControl ItemsSource="{Binding JobHistoryEntries}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="models:JobHistoryEntry">
<Border Classes="tr" Padding="7">
<Grid ColumnDefinitions="1.3*,1.6*,1.4*,2*,0.9*,2.4*">
<TextBlock Grid.Column="0" Classes="td"
Text="{Binding Time, StringFormat='{}{0:dd.MM. HH:mm}'}" />
<TextBlock Grid.Column="1" Classes="td" Text="{Binding JobName}" />
<TextBlock Grid.Column="2" Classes="td" Text="{Binding Agent}" />
<TextBlock Grid.Column="3" Classes="td dim" Text="{Binding JobDescription}" />
<Border Grid.Column="4" Classes="tag"
Classes.ok="{Binding Status,
Converter={x:Static conv:StringEqualsConverter.Instance},
ConverterParameter=Success}"
Classes.bad="{Binding Status,
Converter={x:Static conv:StringEqualsConverter.Instance},
ConverterParameter=Error}">
<TextBlock Text="{Binding Status}" />
</Border>
<TextBlock Grid.Column="5" Classes="td dim" Text="{Binding Info}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
</Panel>
</DockPanel>
</UserControl>