Files
Sharepoint-Toolbox/SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml
Dev db42047db1 feat(16-01): add Export Options GroupBox with MergePermissions checkbox to both XAML views
- Added Export Options GroupBox after Scan Options in UserAccessAuditView.xaml
- Added Export Options GroupBox after Display Options in PermissionsView.xaml
- Both checkboxes bind to MergePermissions with localized labels via TranslationSource
2026-04-09 12:32:08 +02:00

425 lines
21 KiB
XML

<UserControl x:Class="SharepointToolbox.Views.Tabs.UserAccessAuditView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:SharepointToolbox.Localization">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="290" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Left panel -->
<DockPanel Grid.Column="0" Grid.Row="0" Margin="8">
<!-- Mode toggle -->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,0,0,8">
<RadioButton Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.mode.search]}"
IsChecked="{Binding IsBrowseMode, Converter={StaticResource InverseBoolConverter}}"
Margin="0,0,12,0" />
<RadioButton Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.mode.browse]}"
IsChecked="{Binding IsBrowseMode}" />
</StackPanel>
<!-- SEARCH MODE PANEL (visible when IsBrowseMode=false) -->
<GroupBox DockPanel.Dock="Top" Margin="0,0,0,8" Padding="8">
<GroupBox.Header>
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.grp.users]}" />
</GroupBox.Header>
<GroupBox.Style>
<Style TargetType="GroupBox">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsBrowseMode}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</GroupBox.Style>
<StackPanel>
<TextBox Text="{Binding SearchQuery, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,2" />
<TextBlock Text="Searching..." FontStyle="Italic" FontSize="10" Foreground="Gray" Margin="0,0,0,2">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSearching}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<ListBox x:Name="SearchResultsListBox"
ItemsSource="{Binding SearchResults}" MaxHeight="120"
ScrollViewer.VerticalScrollBarVisibility="Auto" Margin="0,0,0,4"
SelectionChanged="SearchResultsListBox_SelectionChanged">
<ListBox.Style>
<Style TargetType="ListBox">
<Style.Triggers>
<DataTrigger Binding="{Binding SearchResults.Count}" Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="DisplayName" />
<Binding Path="Mail" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</GroupBox>
<!-- BROWSE MODE PANEL (visible when IsBrowseMode=true) -->
<GroupBox DockPanel.Dock="Top" Margin="0,0,0,8" Padding="8"
Visibility="{Binding IsBrowseMode, Converter={StaticResource BoolToVisibilityConverter}}">
<GroupBox.Header>
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[directory.grp.browse]}" />
</GroupBox.Header>
<DockPanel>
<!-- Load/Cancel buttons -->
<Grid DockPanel.Dock="Top" Margin="0,0,0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[directory.btn.load]}"
Command="{Binding LoadDirectoryCommand}" Margin="0,0,4,0" Padding="6,3" />
<Button Grid.Column="1"
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[directory.btn.cancel]}"
Command="{Binding CancelDirectoryLoadCommand}" Padding="6,3" />
</Grid>
<!-- Include guests checkbox -->
<CheckBox DockPanel.Dock="Top"
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[directory.chk.guests]}"
IsChecked="{Binding IncludeGuests}" Margin="0,0,0,4" />
<!-- Filter text -->
<TextBox DockPanel.Dock="Top"
Text="{Binding DirectoryFilterText, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,4" />
<!-- Status row: load status + user count -->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,0,0,4">
<TextBlock Text="{Binding DirectoryLoadStatus}" FontStyle="Italic" Foreground="Gray" FontSize="10"
Margin="0,0,8,0" />
<TextBlock FontSize="10" Foreground="Gray">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="DirectoryUserCount" />
<Binding Source="{x:Static loc:TranslationSource.Instance}" Path="[directory.status.count]" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
<!-- Hint text -->
<TextBlock DockPanel.Dock="Bottom"
Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[directory.hint.doubleclick]}"
FontStyle="Italic" Foreground="Gray" FontSize="10" Margin="0,4,0,0"
TextWrapping="Wrap" />
<!-- Directory DataGrid -->
<DataGrid x:Name="DirectoryDataGrid"
ItemsSource="{Binding DirectoryUsersView}"
AutoGenerateColumns="False" IsReadOnly="True"
VirtualizingPanel.IsVirtualizing="True" EnableRowVirtualization="True"
MouseDoubleClick="DirectoryDataGrid_MouseDoubleClick"
CanUserSortColumns="True"
SelectionMode="Single" SelectionUnit="FullRow"
HeadersVisibility="Column" GridLinesVisibility="Horizontal"
BorderThickness="1" BorderBrush="#DDDDDD">
<DataGrid.Columns>
<DataGridTextColumn Header="Name"
Binding="{Binding DisplayName}" Width="120" />
<DataGridTextColumn Header="Email"
Binding="{Binding UserPrincipalName}" Width="140" />
<DataGridTextColumn Header="Department"
Binding="{Binding Department}" Width="90" />
<DataGridTextColumn Header="Job Title"
Binding="{Binding JobTitle}" Width="90" />
<DataGridTemplateColumn Header="Type" Width="60">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding UserType}" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding UserType}" Value="Guest">
<Setter Property="Foreground" Value="#F39C12" />
<Setter Property="FontWeight" Value="SemiBold" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</GroupBox>
<!-- SHARED: Selected users (visible in both modes) -->
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,8">
<ItemsControl ItemsSource="{Binding SelectedUsers}" Margin="0,0,0,4">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="#EBF5FB" BorderBrush="#2980B9" BorderThickness="1"
CornerRadius="4" Padding="6,2" Margin="0,1">
<DockPanel>
<Button Content="x" DockPanel.Dock="Right" Padding="4,0"
Background="Transparent" BorderThickness="0"
Command="{Binding DataContext.RemoveUserCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}" />
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center" />
</DockPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="{Binding SelectedUsersLabel}" FontStyle="Italic" Foreground="Gray" FontSize="10" />
</StackPanel>
<!-- Scan Options (always visible) -->
<GroupBox Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.grp.options]}"
DockPanel.Dock="Top" Margin="0,0,0,8" Padding="8">
<StackPanel>
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.inherited.perms]}"
IsChecked="{Binding IncludeInherited}" Margin="0,0,0,4" />
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.scan.folders]}"
IsChecked="{Binding ScanFolders}" Margin="0,0,0,4" />
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.recursive]}"
IsChecked="{Binding IncludeSubsites}" Margin="0,0,0,0" />
</StackPanel>
</GroupBox>
<!-- Export Options (always visible) -->
<GroupBox Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.grp.export]}"
DockPanel.Dock="Top" Margin="0,0,0,8" Padding="8">
<StackPanel>
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.merge.permissions]}"
IsChecked="{Binding MergePermissions}" />
</StackPanel>
</GroupBox>
<!-- Run/Export buttons (always visible) -->
<StackPanel DockPanel.Dock="Top">
<Grid Margin="0,0,0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.btn.run]}"
Command="{Binding RunCommand}" Margin="0,0,4,0" Padding="6,3" />
<Button Grid.Column="1"
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[btn.cancel]}"
Command="{Binding CancelCommand}" Margin="0,0,0,0" Padding="6,3" />
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.btn.exportCsv]}"
Command="{Binding ExportCsvCommand}" Margin="0,0,4,0" Padding="6,3" />
<Button Grid.Column="1"
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.btn.exportHtml]}"
Command="{Binding ExportHtmlCommand}" Margin="0,0,0,0" Padding="6,3" />
</Grid>
</StackPanel>
</DockPanel>
<!-- Right panel -->
<Grid Grid.Column="1" Grid.Row="0" Margin="0,8,8,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,8">
<Border Background="#EBF5FB" BorderBrush="#2980B9" BorderThickness="1"
CornerRadius="4" Padding="12,6" Margin="0,0,8,0">
<StackPanel HorizontalAlignment="Center">
<TextBlock Text="{Binding TotalAccessCount}" FontSize="20" FontWeight="Bold" HorizontalAlignment="Center" />
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.summary.total]}"
FontSize="10" Foreground="Gray" HorizontalAlignment="Center" />
</StackPanel>
</Border>
<Border Background="#EAFAF1" BorderBrush="#27AE60" BorderThickness="1"
CornerRadius="4" Padding="12,6" Margin="0,0,8,0">
<StackPanel HorizontalAlignment="Center">
<TextBlock Text="{Binding SitesCount}" FontSize="20" FontWeight="Bold" HorizontalAlignment="Center" />
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.summary.sites]}"
FontSize="10" Foreground="Gray" HorizontalAlignment="Center" />
</StackPanel>
</Border>
<Border Background="#FDEDEC" BorderBrush="#E74C3C" BorderThickness="1"
CornerRadius="4" Padding="12,6">
<StackPanel HorizontalAlignment="Center">
<TextBlock Text="{Binding HighPrivilegeCount}" FontSize="20" FontWeight="Bold"
HorizontalAlignment="Center" Foreground="#C0392B" />
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.summary.highPriv]}"
FontSize="10" Foreground="Gray" HorizontalAlignment="Center" />
</StackPanel>
</Border>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,4">
<TextBox Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}" Width="200" Margin="0,0,8,0" />
<ToggleButton IsChecked="{Binding IsGroupByUser}" Padding="8,3">
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Setter Property="Content" Value="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.toggle.bySite]}" />
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.toggle.byUser]}" />
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
</StackPanel>
<DataGrid Grid.Row="2" ItemsSource="{Binding ResultsView}"
AutoGenerateColumns="False" IsReadOnly="True"
VirtualizingPanel.IsVirtualizing="True" EnableRowVirtualization="True">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding AccessType}" Value="Direct">
<Setter Property="Background" Value="#EBF5FB" />
</DataTrigger>
<DataTrigger Binding="{Binding AccessType}" Value="Group">
<Setter Property="Background" Value="#EAFAF1" />
</DataTrigger>
<DataTrigger Binding="{Binding AccessType}" Value="Inherited">
<Setter Property="Background" Value="#F4F6F6" />
</DataTrigger>
<DataTrigger Binding="{Binding IsHighPrivilege}" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="4,2">
<TextBlock Text="{Binding Name}" FontWeight="Bold" Margin="0,0,8,0" />
<TextBlock Text="{Binding ItemCount, StringFormat=({0})}" Foreground="Gray" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Header="User" Width="180">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding UserLogin}" VerticalAlignment="Center" />
<Border Background="#F39C12" CornerRadius="3" Padding="4,1" Margin="6,0,0,0"
VerticalAlignment="Center">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsExternalUser}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="Guest" FontSize="10" Foreground="White" FontWeight="SemiBold" />
</Border>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Site" Binding="{Binding SiteTitle}" Width="120" />
<DataGridTextColumn Header="Object" Binding="{Binding ObjectTitle}" Width="140" />
<DataGridTextColumn Header="Object Type" Binding="{Binding ObjectType}" Width="90" />
<DataGridTemplateColumn Header="Permission Level" Width="140">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#x26A0;" Foreground="#E74C3C" Margin="0,0,4,0"
FontSize="12" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsHighPrivilege}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="{Binding PermissionLevel}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Access Type" Width="110">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding AccessType}" />
<Style.Triggers>
<DataTrigger Binding="{Binding AccessType}" Value="Direct">
<Setter Property="Foreground" Value="#1A5276" />
</DataTrigger>
<DataTrigger Binding="{Binding AccessType}" Value="Group">
<Setter Property="Foreground" Value="#1E8449" />
</DataTrigger>
<DataTrigger Binding="{Binding AccessType}" Value="Inherited">
<Setter Property="Foreground" Value="#626567" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Granted Through" Binding="{Binding GrantedThrough}" Width="140" />
</DataGrid.Columns>
</DataGrid>
</Grid>
<!-- Status bar -->
<StatusBar Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1">
<StatusBarItem>
<ProgressBar Width="150" Height="14" Value="{Binding ProgressValue}" Minimum="0" Maximum="100" />
</StatusBarItem>
<StatusBarItem Content="{Binding StatusMessage}" />
</StatusBar>
</Grid>
</UserControl>