Public Access
Merge branch 'main' into the Android head
Main grew the screens the host-management plan called for — hosts, pins, snippets, logs, import, teams — plus the ObjectStore and Import projects behind two of them, and moved WindowsDeviceKeyStore into the desktop head's Platform folder. Five of those view models landed in a directory this branch had already moved, so they join the rest in DodoSSH.Client.Shell: git spotted the rename and put them there, and the namespaces followed. Shell picks up ObjectStore and Import as a result, which the Android head then gets transitively and will use neither of at first — scoped storage means there is no ~/.ssh/config to import, and file transfer is out of its first scope. Desktop suites green at 155 and 64.
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:DodoSSH.Client.Shell.ViewModels"
|
||||
x:Class="DodoSSH.Client.App.Views.TeamsScreen"
|
||||
x:DataType="vm:TeamsViewModel">
|
||||
|
||||
<!--
|
||||
Teams.
|
||||
|
||||
The screen is built around one fact that every other product in this category hides: adding somebody to
|
||||
a team and giving them a vault key are two different acts, and only the first is something a server can
|
||||
do. The second needs a machine that holds the key, because this server never does. So the members table
|
||||
and the vaults table are side by side, an addition says out loud that it granted nothing readable yet,
|
||||
and SHARE KEY is its own button rather than a checkbox on the member row.
|
||||
|
||||
What the design asked for and is still not here: pending invitations (there is no outbound mail path and
|
||||
no invitation token), two-factor state and last-active (the server records neither), and avatars (no
|
||||
picture is stored anywhere). None of them is drawn with invented data.
|
||||
-->
|
||||
|
||||
<Grid ColumnDefinitions="268,*">
|
||||
|
||||
<!-- ============ The team list ============ -->
|
||||
<Border Grid.Column="0" BorderBrush="{StaticResource Border}" BorderThickness="0,0,1,0">
|
||||
<Grid RowDefinitions="44,*,Auto">
|
||||
|
||||
<Border Grid.Row="0" Padding="14,0" BorderBrush="{StaticResource Border}" BorderThickness="0,0,0,1">
|
||||
<Grid ColumnDefinitions="*,Auto" VerticalAlignment="Center">
|
||||
<TextBlock Grid.Column="0" Classes="mono" Text="TEAMS" FontSize="11" FontWeight="SemiBold"
|
||||
LetterSpacing="1" Foreground="{StaticResource Text}" VerticalAlignment="Center" />
|
||||
<Button Grid.Column="1" Classes="ghost" Content="NEW"
|
||||
Command="{Binding NewTeamCommand}" IsEnabled="{Binding !IsBusy}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<StackPanel>
|
||||
<ListBox ItemsSource="{Binding Teams}" SelectedItem="{Binding SelectedTeam}"
|
||||
Background="Transparent" BorderThickness="0">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TeamRowViewModel">
|
||||
<StackPanel Spacing="2" Margin="0,3">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Text="{Binding Name}" FontSize="12" FontWeight="Medium"
|
||||
Foreground="{StaticResource Text}" TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding Role}" FontSize="9"
|
||||
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<TextBlock Classes="hint" FontSize="10" Text="{Binding Detail}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<TextBlock Classes="hint" FontSize="10" Margin="14,12" TextWrapping="Wrap"
|
||||
IsVisible="{Binding !HasTeams}"
|
||||
Text="No teams yet. A team is what makes a vault shareable: its vaults can be opened by every member you wrap a key to." />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- The create form, in place rather than in a modal: this window has no idiom for one. -->
|
||||
<Border Grid.Row="2" Padding="14,12" BorderBrush="{StaticResource Border}" BorderThickness="0,1,0,0"
|
||||
IsVisible="{Binding IsCreatingTeam}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Classes="label" Text="NEW TEAM" />
|
||||
<TextBox PlaceholderText="Name" Text="{Binding NewTeamName}" />
|
||||
<TextBox PlaceholderText="slug-for-urls" Text="{Binding NewTeamSlug}" />
|
||||
<TextBlock Classes="hint" FontSize="9.5" TextWrapping="Wrap"
|
||||
Text="The slug is lowercase letters, digits and hyphens, and has to be unique across this server." />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="accent" Content="CREATE" Command="{Binding CreateTeamCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
<Button Classes="ghost" Content="CANCEL" Command="{Binding CancelNewTeamCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ============ Members and vaults ============ -->
|
||||
<Grid Grid.Column="1" RowDefinitions="44,*,Auto">
|
||||
|
||||
<Border Grid.Row="0" Padding="14,0" BorderBrush="{StaticResource Border}" BorderThickness="0,0,0,1">
|
||||
<TextBlock Classes="mono" Text="{Binding SelectedTeam.Name}" FontSize="11" FontWeight="SemiBold"
|
||||
LetterSpacing="1" Foreground="{StaticResource Text}" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
|
||||
<ScrollViewer Grid.Row="1" IsVisible="{Binding HasSelection}">
|
||||
<StackPanel Margin="14,14" Spacing="18">
|
||||
|
||||
<!-- Members -->
|
||||
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Classes="label" Text="MEMBERS" />
|
||||
|
||||
<ListBox ItemsSource="{Binding Members}" SelectedItem="{Binding SelectedMember}"
|
||||
Background="Transparent" BorderThickness="0" MaxHeight="240">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TeamMemberRowViewModel">
|
||||
<Grid ColumnDefinitions="*,150,Auto" Margin="0,3">
|
||||
<StackPanel Grid.Column="0" Spacing="2">
|
||||
<TextBlock Text="{Binding Name}" FontSize="12" FontWeight="Medium"
|
||||
Foreground="{StaticResource Text}" TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Classes="hint" FontSize="10" Text="{Binding Email}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1" Classes="hint" FontSize="10" VerticalAlignment="Center"
|
||||
Text="{Binding KeyState}" TextWrapping="Wrap" />
|
||||
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding Role}" FontSize="9"
|
||||
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center"
|
||||
Margin="10,0,0,0" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto,Auto" IsVisible="{Binding CanAdministerSelected}">
|
||||
<TextBox Grid.Column="0" PlaceholderText="colleague@example.com" Text="{Binding InviteEmail}"
|
||||
Margin="0,0,6,0" />
|
||||
<Button Grid.Column="1" Classes="accent" Content="ADD MEMBER"
|
||||
Command="{Binding AddMemberCommand}" IsEnabled="{Binding !IsBusy}" />
|
||||
<Button Grid.Column="2" Classes="danger" Content="REMOVE" Margin="6,0,0,0"
|
||||
Command="{Binding RemoveMemberCommand}" IsEnabled="{Binding !IsBusy}"
|
||||
ToolTip.Tip="Removes the selected member and withdraws every vault key they hold from this team. It blocks future reads only — anything already on their machine stays there, so rotate the credentials that matter." />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Classes="hint" FontSize="9.5" TextWrapping="Wrap"
|
||||
IsVisible="{Binding CanAdministerSelected}"
|
||||
Text="Adding somebody lets the server serve them this team's vaults. It does not let them read one: a vault key can only be wrapped by a machine that already holds it, which is what SHARE KEY below does." />
|
||||
</StackPanel>
|
||||
|
||||
<Border Height="1" Background="{StaticResource BorderSubtle}" />
|
||||
|
||||
<!-- Vaults -->
|
||||
|
||||
<StackPanel Spacing="8">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="label" Text="VAULTS" VerticalAlignment="Center" />
|
||||
<Button Grid.Column="1" Classes="ghost" Content="NEW VAULT"
|
||||
Command="{Binding CreateVaultCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" IsVisible="{Binding CanAdministerSelected}" />
|
||||
</Grid>
|
||||
|
||||
<ListBox ItemsSource="{Binding Vaults}" SelectedItem="{Binding SelectedVault}"
|
||||
Background="Transparent" BorderThickness="0" MaxHeight="200">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TeamVaultRowViewModel">
|
||||
<StackPanel Spacing="2" Margin="0,3">
|
||||
<TextBlock Text="{Binding Name}" FontSize="12" FontWeight="Medium"
|
||||
Foreground="{StaticResource Text}" />
|
||||
<TextBlock Classes="hint" FontSize="10" Text="{Binding State}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<TextBlock Classes="hint" FontSize="10" TextWrapping="Wrap"
|
||||
IsVisible="{Binding !HasSelection}"
|
||||
Text="Select a team to see its vaults." />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="accent" Content="SHARE KEY" Command="{Binding ShareVaultCommand}"
|
||||
IsEnabled="{Binding !IsBusy}"
|
||||
ToolTip.Tip="Wraps the selected vault's key to the selected member. Their published key is checked against the server's append-only key log first, and nothing is wrapped if it does not appear there unchanged." />
|
||||
<Button Classes="danger" Content="WITHDRAW KEY" Command="{Binding RevokeVaultCommand}"
|
||||
IsEnabled="{Binding !IsBusy}"
|
||||
ToolTip.Tip="Withdraws the selected member's key to the selected vault. Blocks future reads only." />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Classes="hint" FontSize="9.5" TextWrapping="Wrap"
|
||||
Text="Sharing verifies the recipient's key against the key log, which proves this server has been consistent with itself — not that the key is the right person's. Compare the fingerprint with them over a channel this server does not carry before sharing anything that matters." />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<TextBlock Grid.Row="1" Classes="hint" FontSize="11" Margin="20" TextWrapping="Wrap"
|
||||
VerticalAlignment="Top" IsVisible="{Binding !HasSelection}"
|
||||
Text="Create a team on the left, or wait to be added to one. A team owns vaults; a vault's key is what makes its contents readable, and that key is handed out by people rather than by the server." />
|
||||
|
||||
<Border Grid.Row="2" Padding="14,10" BorderBrush="{StaticResource Border}" BorderThickness="0,1,0,0"
|
||||
IsVisible="{Binding Status, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||
<TextBlock Classes="hint" FontSize="10.5" Text="{Binding Status}" TextWrapping="Wrap" />
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
Reference in New Issue
Block a user