Public Access
Merge branch 'claude/adding-hosts-phone-docs-e472b0'
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:DodoSSH.Client.Shell.ViewModels"
|
||||
xmlns:contracts="using:DodoSSH.Contracts"
|
||||
x:Class="DodoSSH.Client.App.Views.TeamsScreen"
|
||||
x:DataType="vm:TeamsViewModel">
|
||||
|
||||
@@ -13,9 +14,12 @@
|
||||
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.
|
||||
What the design asked for and is still not here: two-factor state (no such concept exists anywhere in
|
||||
this product) and avatars (no picture is stored anywhere). Invitations and last-active are here, and
|
||||
both are narrower than the design drew. Nothing is sent — there is no outbound mail path and no token,
|
||||
so an invitation is a standing instruction that the next account signing in with that address joins the
|
||||
team, and there is consequently nothing to resend. Last-active is recorded at most once per account per
|
||||
hour, so it is drawn coarsely rather than to the minute. None of it is drawn with invented data.
|
||||
-->
|
||||
|
||||
<Grid ColumnDefinitions="268,*">
|
||||
@@ -82,13 +86,71 @@
|
||||
<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" />
|
||||
<Grid ColumnDefinitions="*,Auto" VerticalAlignment="Center">
|
||||
<TextBlock Grid.Column="0" Classes="mono" Text="{Binding SelectedTeam.Name}" FontSize="11"
|
||||
FontWeight="SemiBold" LetterSpacing="1" Foreground="{StaticResource Text}"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<!--
|
||||
The team's own operations. RENAME is an admin's; the other two are the owner's alone, and
|
||||
that is the line the server draws as well — an admin the owner promoted must not be able
|
||||
to archive the team or take it from them.
|
||||
-->
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="6"
|
||||
IsVisible="{Binding ShowsTeamActions}">
|
||||
<Button Classes="ghost" Content="RENAME" Command="{Binding RenameTeamCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" IsVisible="{Binding CanAdministerSelected}" />
|
||||
<Button Classes="ghost" Content="HAND OVER" Command="{Binding TransferOwnershipCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" IsVisible="{Binding OwnsSelected}"
|
||||
ToolTip.Tip="Hands this team to the selected member. They become the owner and you become an admin; only the new owner can hand it on again." />
|
||||
<Button Classes="danger" Content="ARCHIVE" Command="{Binding ArchiveTeamCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" IsVisible="{Binding OwnsSelected}"
|
||||
ToolTip.Tip="Takes the team out of every member's list. Refused while it still owns any vault, and only somebody with database access can bring it back." />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<ScrollViewer Grid.Row="1" IsVisible="{Binding HasSelection}">
|
||||
<StackPanel Margin="14,14" Spacing="18">
|
||||
|
||||
<!-- The rename form, in place, exactly as the create form on the left is. -->
|
||||
<Border Padding="12" CornerRadius="4" BorderThickness="1"
|
||||
BorderBrush="{StaticResource Border}" IsVisible="{Binding IsEditingTeam}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Classes="label" Text="RENAME TEAM" />
|
||||
<TextBox PlaceholderText="Name" Text="{Binding EditTeamName}" />
|
||||
<TextBox PlaceholderText="What this team is for (optional)"
|
||||
Text="{Binding EditTeamDescription}" />
|
||||
<TextBlock Classes="hint" FontSize="9.5" TextWrapping="Wrap"
|
||||
Text="The slug does not change. It is what URLs and the server's own records use, and it is unique only among live teams — so a rename that moved it could take one an archived team is still holding." />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="accent" Content="SAVE" Command="{Binding SaveTeamCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
<Button Classes="ghost" Content="CANCEL" Command="{Binding CancelRenameTeamCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!--
|
||||
The armed confirmation, drawn where the buttons that armed it were. The vault screen's
|
||||
idiom, and for the same reason: there is no modal anywhere in this window.
|
||||
-->
|
||||
<Border Background="{StaticResource DangerWash}" BorderBrush="{StaticResource DangerSoft}"
|
||||
BorderThickness="1" CornerRadius="4" Padding="12"
|
||||
IsVisible="{Binding IsConfirming}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="{Binding PendingAction.Question}" FontSize="12" FontWeight="Medium"
|
||||
Foreground="{StaticResource Text}" TextWrapping="Wrap" />
|
||||
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
|
||||
Text="{Binding PendingAction.Consequence}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="danger" Content="CONFIRM" Command="{Binding ConfirmActionCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
<Button Classes="ghost" Content="CANCEL" Command="{Binding CancelActionCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Members -->
|
||||
|
||||
<StackPanel Spacing="8">
|
||||
@@ -98,14 +160,17 @@
|
||||
Background="Transparent" BorderThickness="0" MaxHeight="240">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TeamMemberRowViewModel">
|
||||
<Grid ColumnDefinitions="*,150,Auto" Margin="0,3">
|
||||
<Grid ColumnDefinitions="*,168,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" />
|
||||
<StackPanel Grid.Column="1" Spacing="2" VerticalAlignment="Center">
|
||||
<TextBlock Classes="hint" FontSize="10" Text="{Binding KeyState}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock Classes="hint" FontSize="9.5" Text="{Binding LastActive}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding Role}" FontSize="9"
|
||||
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center"
|
||||
Margin="10,0,0,0" />
|
||||
@@ -114,21 +179,96 @@
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<!--
|
||||
Buttons and a command rather than a selector bound to the role, which is the choice the
|
||||
key editor and the category rail already make and for the reason they record: a selector
|
||||
moves its own highlight before anything can refuse, so it can end up showing a role
|
||||
nobody was given. OWNER is absent because it is not a role that can be assigned —
|
||||
handing the team over is its own act, with its own confirmation.
|
||||
-->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding CanAdministerSelected}">
|
||||
<TextBlock Classes="label" Text="SET THE SELECTED MEMBER'S ROLE" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="flat choice" Content="VIEWER" Command="{Binding ChangeRoleCommand}"
|
||||
CommandParameter="{x:Static contracts:TeamMemberRole.Viewer}"
|
||||
IsEnabled="{Binding !IsBusy}"
|
||||
ToolTip.Tip="May pull this team's vaults and may not push. It does not withdraw a vault key they already hold." />
|
||||
<Button Classes="flat choice" Content="MEMBER" Command="{Binding ChangeRoleCommand}"
|
||||
CommandParameter="{x:Static contracts:TeamMemberRole.Member}"
|
||||
IsEnabled="{Binding !IsBusy}"
|
||||
ToolTip.Tip="May read and change this team's vaults." />
|
||||
<Button Classes="flat choice" Content="ADMIN" Command="{Binding ChangeRoleCommand}"
|
||||
CommandParameter="{x:Static contracts:TeamMemberRole.Admin}"
|
||||
IsEnabled="{Binding !IsBusy}"
|
||||
ToolTip.Tip="May also manage members, create vaults and share vault keys." />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<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}" />
|
||||
Command="{Binding AddMemberCommand}" IsEnabled="{Binding !IsBusy}"
|
||||
ToolTip.Tip="Adds the account with this address, or invites the address if there is no account here yet. Nothing is sent either way — tell them yourself." />
|
||||
<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>
|
||||
|
||||
<StackPanel Spacing="4" IsVisible="{Binding CanAdministerSelected}">
|
||||
<TextBlock Classes="label" Text="THEY ARRIVE AS" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="flat choice" Content="VIEWER" Classes.active="{Binding AddsAsViewer}"
|
||||
Command="{Binding ChooseNewMemberRoleCommand}"
|
||||
CommandParameter="{x:Static contracts:TeamMemberRole.Viewer}" />
|
||||
<Button Classes="flat choice" Content="MEMBER" Classes.active="{Binding AddsAsMember}"
|
||||
Command="{Binding ChooseNewMemberRoleCommand}"
|
||||
CommandParameter="{x:Static contracts:TeamMemberRole.Member}" />
|
||||
<Button Classes="flat choice" Content="ADMIN" Classes.active="{Binding AddsAsAdmin}"
|
||||
Command="{Binding ChooseNewMemberRoleCommand}"
|
||||
CommandParameter="{x:Static contracts:TeamMemberRole.Admin}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<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>
|
||||
|
||||
<!--
|
||||
Invitations, drawn only when there are any. An empty INVITED heading on every team would be
|
||||
a permanent reminder of a feature most teams never use.
|
||||
-->
|
||||
<StackPanel Spacing="8" IsVisible="{Binding HasInvitations}">
|
||||
<Border Height="1" Background="{StaticResource BorderSubtle}" />
|
||||
|
||||
<TextBlock Classes="label" Text="INVITED" />
|
||||
|
||||
<ListBox ItemsSource="{Binding Invitations}" SelectedItem="{Binding SelectedInvitation}"
|
||||
Background="Transparent" BorderThickness="0" MaxHeight="160">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TeamInvitationRowViewModel">
|
||||
<Grid ColumnDefinitions="*,Auto" Margin="0,3">
|
||||
<StackPanel Grid.Column="0" Spacing="2">
|
||||
<TextBlock Text="{Binding Email}" FontSize="12" FontWeight="Medium"
|
||||
Foreground="{StaticResource Text}" TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Classes="hint" FontSize="10" Text="{Binding State}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding Role}" FontSize="9"
|
||||
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center"
|
||||
Margin="10,0,0,0" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<Button Classes="danger" Content="WITHDRAW INVITATION" HorizontalAlignment="Left"
|
||||
Command="{Binding RevokeInvitationCommand}" IsEnabled="{Binding !IsBusy}"
|
||||
IsVisible="{Binding CanAdministerSelected}"
|
||||
ToolTip.Tip="Signing in with that address will no longer put them in this team. An invitation already taken up is a membership — remove the member instead." />
|
||||
</StackPanel>
|
||||
|
||||
<Border Height="1" Background="{StaticResource BorderSubtle}" />
|
||||
|
||||
<!-- Vaults -->
|
||||
@@ -167,6 +307,34 @@
|
||||
ToolTip.Tip="Withdraws the selected member's key to the selected vault. Blocks future reads only." />
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
Who can open the selected vault — the design's "shared with" avatars, as names and a
|
||||
state. Under the vault rather than beside the member, because a grant is per vault: a
|
||||
count on a member row would imply per-item sharing, which is M5 and does not exist.
|
||||
Withdrawn and stale grants stay listed and say which they are, because a list that
|
||||
quietly dropped them would show a departed colleague as merely absent rather than as
|
||||
somebody whose key was taken away. The dot is Live and means exactly what it says: this
|
||||
person can open this vault right now.
|
||||
-->
|
||||
<TextBlock Classes="label" Text="KEY HOLDERS" />
|
||||
|
||||
<ListBox ItemsSource="{Binding Grants}" Background="Transparent" BorderThickness="0"
|
||||
MaxHeight="150">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TeamGrantRowViewModel">
|
||||
<Grid ColumnDefinitions="10,*" Margin="0,3">
|
||||
<Ellipse Grid.Column="0" Width="6" Height="6" VerticalAlignment="Center"
|
||||
IsVisible="{Binding IsLive}" Fill="{StaticResource Live}" />
|
||||
<StackPanel Grid.Column="1" Spacing="2" Margin="6,0,0,0">
|
||||
<TextBlock Text="{Binding Name}" FontSize="12" FontWeight="Medium"
|
||||
Foreground="{StaticResource Text}" TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Classes="hint" FontSize="10" Text="{Binding State}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user