Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd49de5b

Browse files
authored
feat: add vpn start progress (#114)
1 parent74b8658 commitd49de5b

File tree

14 files changed

+464
-68
lines changed

14 files changed

+464
-68
lines changed

‎.idea/.idea.Coder.Desktop/.idea/projectSettingsUpdater.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎App/Models/RpcModel.cs

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
usingSystem;
12
usingSystem.Collections.Generic;
3+
usingSystem.Diagnostics;
4+
usingCoder.Desktop.App.Converters;
25
usingCoder.Desktop.Vpn.Proto;
36

47
namespaceCoder.Desktop.App.Models;
@@ -19,11 +22,168 @@ public enum VpnLifecycle
1922
Stopping,
2023
}
2124

25+
publicenumVpnStartupStage
26+
{
27+
Unknown,
28+
Initializing,
29+
Downloading,
30+
Finalizing,
31+
}
32+
33+
publicclassVpnDownloadProgress
34+
{
35+
publiculongBytesWritten{get;set;}=0;
36+
publiculong?BytesTotal{get;set;}=null;// null means unknown total size
37+
38+
publicdoubleProgress
39+
{
40+
get
41+
{
42+
if(BytesTotalis>0)
43+
{
44+
return(double)BytesWritten/BytesTotal.Value;
45+
}
46+
return0.0;
47+
}
48+
}
49+
50+
publicoverridestringToString()
51+
{
52+
// TODO: it would be nice if the two suffixes could match
53+
vars=FriendlyByteConverter.FriendlyBytes(BytesWritten);
54+
if(BytesTotal!=null)
55+
s+=$" of{FriendlyByteConverter.FriendlyBytes(BytesTotal.Value)}";
56+
else
57+
s+=" of unknown";
58+
if(BytesTotal!=null)
59+
s+=$" ({Progress:0%})";
60+
returns;
61+
}
62+
63+
publicVpnDownloadProgressClone()
64+
{
65+
returnnewVpnDownloadProgress
66+
{
67+
BytesWritten=BytesWritten,
68+
BytesTotal=BytesTotal,
69+
};
70+
}
71+
72+
publicstaticVpnDownloadProgressFromProto(StartProgressDownloadProgressproto)
73+
{
74+
returnnewVpnDownloadProgress
75+
{
76+
BytesWritten=proto.BytesWritten,
77+
BytesTotal=proto.HasBytesTotal?proto.BytesTotal:null,
78+
};
79+
}
80+
}
81+
82+
publicclassVpnStartupProgress
83+
{
84+
publicconststringDefaultStartProgressMessage="Starting Coder Connect...";
85+
86+
// Scale the download progress to an overall progress value between these
87+
// numbers.
88+
privateconstdoubleDownloadProgressMin=0.05;
89+
privateconstdoubleDownloadProgressMax=0.80;
90+
91+
publicVpnStartupStageStage{get;init;}=VpnStartupStage.Unknown;
92+
publicVpnDownloadProgress?DownloadProgress{get;init;}=null;
93+
94+
// 0.0 to 1.0
95+
publicdoubleProgress
96+
{
97+
get
98+
{
99+
switch(Stage)
100+
{
101+
caseVpnStartupStage.Unknown:
102+
caseVpnStartupStage.Initializing:
103+
return0.0;
104+
caseVpnStartupStage.Downloading:
105+
varprogress=DownloadProgress?.Progress??0.0;
106+
returnDownloadProgressMin+(DownloadProgressMax-DownloadProgressMin)*progress;
107+
caseVpnStartupStage.Finalizing:
108+
returnDownloadProgressMax;
109+
default:
110+
thrownewArgumentOutOfRangeException();
111+
}
112+
}
113+
}
114+
115+
publicoverridestringToString()
116+
{
117+
switch(Stage)
118+
{
119+
caseVpnStartupStage.Unknown:
120+
caseVpnStartupStage.Initializing:
121+
returnDefaultStartProgressMessage;
122+
caseVpnStartupStage.Downloading:
123+
vars="Downloading Coder Connect binary...";
124+
if(DownloadProgressis notnull)
125+
{
126+
s+="\n"+DownloadProgress;
127+
}
128+
129+
returns;
130+
caseVpnStartupStage.Finalizing:
131+
return"Finalizing Coder Connect startup...";
132+
default:
133+
thrownewArgumentOutOfRangeException();
134+
}
135+
}
136+
137+
publicVpnStartupProgressClone()
138+
{
139+
returnnewVpnStartupProgress
140+
{
141+
Stage=Stage,
142+
DownloadProgress=DownloadProgress?.Clone(),
143+
};
144+
}
145+
146+
publicstaticVpnStartupProgressFromProto(StartProgressproto)
147+
{
148+
returnnewVpnStartupProgress
149+
{
150+
Stage=proto.Stageswitch
151+
{
152+
StartProgressStage.Initializing=>VpnStartupStage.Initializing,
153+
StartProgressStage.Downloading=>VpnStartupStage.Downloading,
154+
StartProgressStage.Finalizing=>VpnStartupStage.Finalizing,
155+
_=>VpnStartupStage.Unknown,
156+
},
157+
DownloadProgress=proto.StageisStartProgressStage.Downloading?
158+
VpnDownloadProgress.FromProto(proto.DownloadProgress):
159+
null,
160+
};
161+
}
162+
}
163+
22164
publicclassRpcModel
23165
{
24166
publicRpcLifecycleRpcLifecycle{get;set;}=RpcLifecycle.Disconnected;
25167

26-
publicVpnLifecycleVpnLifecycle{get;set;}=VpnLifecycle.Unknown;
168+
publicVpnLifecycleVpnLifecycle
169+
{
170+
get;
171+
set
172+
{
173+
if(VpnLifecycle!=value&&value==VpnLifecycle.Starting)
174+
// Reset the startup progress when the VPN lifecycle changes to
175+
// Starting.
176+
VpnStartupProgress=null;
177+
field=value;
178+
}
179+
}
180+
181+
// Nullable because it is only set when the VpnLifecycle is Starting
182+
publicVpnStartupProgress?VpnStartupProgress
183+
{
184+
get=>VpnLifecycleisVpnLifecycle.Starting?field??newVpnStartupProgress():null;
185+
set;
186+
}
27187

28188
publicIReadOnlyList<Workspace>Workspaces{get;set;}=[];
29189

@@ -35,6 +195,7 @@ public RpcModel Clone()
35195
{
36196
RpcLifecycle=RpcLifecycle,
37197
VpnLifecycle=VpnLifecycle,
198+
VpnStartupProgress=VpnStartupProgress?.Clone(),
38199
Workspaces=Workspaces,
39200
Agents=Agents,
40201
};

‎App/Services/RpcController.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,10 @@ public async Task StartVpn(CancellationToken ct = default)
161161
thrownewRpcOperationException(
162162
$"Cannot start VPN without valid credentials, current state:{credentials.State}");
163163

164-
MutateState(state=>{state.VpnLifecycle=VpnLifecycle.Starting;});
164+
MutateState(state=>
165+
{
166+
state.VpnLifecycle=VpnLifecycle.Starting;
167+
});
165168

166169
ServiceMessagereply;
167170
try
@@ -283,15 +286,28 @@ private void ApplyStatusUpdate(Status status)
283286
});
284287
}
285288

289+
privatevoidApplyStartProgressUpdate(StartProgressmessage)
290+
{
291+
MutateState(state=>
292+
{
293+
// The model itself will ignore this value if we're not in the
294+
// starting state.
295+
state.VpnStartupProgress=VpnStartupProgress.FromProto(message);
296+
});
297+
}
298+
286299
privatevoidSpeakerOnReceive(ReplyableRpcMessage<ClientMessage,ServiceMessage>message)
287300
{
288301
switch(message.Message.MsgCase)
289302
{
303+
caseServiceMessage.MsgOneofCase.Start:
304+
caseServiceMessage.MsgOneofCase.Stop:
290305
caseServiceMessage.MsgOneofCase.Status:
291306
ApplyStatusUpdate(message.Message.Status);
292307
break;
293-
caseServiceMessage.MsgOneofCase.Start:
294-
caseServiceMessage.MsgOneofCase.Stop:
308+
caseServiceMessage.MsgOneofCase.StartProgress:
309+
ApplyStartProgressUpdate(message.Message.StartProgress);
310+
break;
295311
caseServiceMessage.MsgOneofCase.None:
296312
default:
297313
// TODO: log unexpected message

‎App/ViewModels/TrayWindowViewModel.cs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ public partial class TrayWindowViewModel : ObservableObject, IAgentExpanderHost
2929
{
3030
privateconstintMaxAgents=5;
3131
privateconststringDefaultDashboardUrl="https://coder.com";
32-
privateconststringDefaultHostnameSuffix=".coder";
3332

3433
privatereadonlyIServiceProvider_services;
3534
privatereadonlyIRpcController_rpcController;
@@ -53,6 +52,7 @@ public partial class TrayWindowViewModel : ObservableObject, IAgentExpanderHost
5352

5453
[ObservableProperty]
5554
[NotifyPropertyChangedFor(nameof(ShowEnableSection))]
55+
[NotifyPropertyChangedFor(nameof(ShowVpnStartProgressSection))]
5656
[NotifyPropertyChangedFor(nameof(ShowWorkspacesHeader))]
5757
[NotifyPropertyChangedFor(nameof(ShowNoAgentsSection))]
5858
[NotifyPropertyChangedFor(nameof(ShowAgentsSection))]
@@ -63,14 +63,33 @@ public partial class TrayWindowViewModel : ObservableObject, IAgentExpanderHost
6363

6464
[ObservableProperty]
6565
[NotifyPropertyChangedFor(nameof(ShowEnableSection))]
66+
[NotifyPropertyChangedFor(nameof(ShowVpnStartProgressSection))]
6667
[NotifyPropertyChangedFor(nameof(ShowWorkspacesHeader))]
6768
[NotifyPropertyChangedFor(nameof(ShowNoAgentsSection))]
6869
[NotifyPropertyChangedFor(nameof(ShowAgentsSection))]
6970
[NotifyPropertyChangedFor(nameof(ShowAgentOverflowButton))]
7071
[NotifyPropertyChangedFor(nameof(ShowFailedSection))]
7172
publicpartialstring?VpnFailedMessage{get;set;}=null;
7273

73-
publicboolShowEnableSection=>VpnFailedMessageisnull&&VpnLifecycleis notVpnLifecycle.Started;
74+
[ObservableProperty]
75+
[NotifyPropertyChangedFor(nameof(VpnStartProgressIsIndeterminate))]
76+
[NotifyPropertyChangedFor(nameof(VpnStartProgressValueOrDefault))]
77+
publicpartialint?VpnStartProgressValue{get;set;}=null;
78+
79+
publicintVpnStartProgressValueOrDefault=>VpnStartProgressValue??0;
80+
81+
[ObservableProperty]
82+
[NotifyPropertyChangedFor(nameof(VpnStartProgressMessageOrDefault))]
83+
publicpartialstring?VpnStartProgressMessage{get;set;}=null;
84+
85+
publicstringVpnStartProgressMessageOrDefault=>
86+
string.IsNullOrEmpty(VpnStartProgressMessage)?VpnStartupProgress.DefaultStartProgressMessage:VpnStartProgressMessage;
87+
88+
publicboolVpnStartProgressIsIndeterminate=>VpnStartProgressValueOrDefault==0;
89+
90+
publicboolShowEnableSection=>VpnFailedMessageisnull&&VpnLifecycleis notVpnLifecycle.Starting and notVpnLifecycle.Started;
91+
92+
publicboolShowVpnStartProgressSection=>VpnFailedMessageisnull&&VpnLifecycleisVpnLifecycle.Starting;
7493

7594
publicboolShowWorkspacesHeader=>VpnFailedMessageisnull&&VpnLifecycleisVpnLifecycle.Started;
7695

@@ -170,6 +189,20 @@ private void UpdateFromRpcModel(RpcModel rpcModel)
170189
VpnLifecycle=rpcModel.VpnLifecycle;
171190
VpnSwitchActive=rpcModel.VpnLifecycleisVpnLifecycle.Starting orVpnLifecycle.Started;
172191

192+
// VpnStartupProgress is only set when the VPN is starting.
193+
if(rpcModel.VpnLifecycleisVpnLifecycle.Starting&&rpcModel.VpnStartupProgress!=null)
194+
{
195+
// Convert 0.00-1.00 to 0-100.
196+
varprogress=(int)(rpcModel.VpnStartupProgress.Progress*100);
197+
VpnStartProgressValue=Math.Clamp(progress,0,100);
198+
VpnStartProgressMessage=rpcModel.VpnStartupProgress.ToString();
199+
}
200+
else
201+
{
202+
VpnStartProgressValue=null;
203+
VpnStartProgressMessage=null;
204+
}
205+
173206
// Add every known agent.
174207
HashSet<ByteString>workspacesWithAgents=[];
175208
List<AgentViewModel>agents=[];

‎App/Views/Pages/TrayWindowLoginRequiredPage.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
</HyperlinkButton>
3737

3838
<HyperlinkButton
39-
Command="{x:Bind ViewModel.ExitCommand, Mode=OneWay}"
39+
Command="{x:Bind ViewModel.ExitCommand}"
4040
Margin="-12,-8,-12,-5"
4141
HorizontalAlignment="Stretch"
4242
HorizontalContentAlignment="Left">

‎App/Views/Pages/TrayWindowMainPage.xaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
<ProgressRing
4444
Grid.Column="1"
4545
IsActive="{x:Bind ViewModel.VpnLifecycle, Converter={StaticResource ConnectingBoolConverter}, Mode=OneWay}"
46+
IsIndeterminate="{x:Bind ViewModel.VpnStartProgressIsIndeterminate, Mode=OneWay}"
47+
Value="{x:Bind ViewModel.VpnStartProgressValueOrDefault, Mode=OneWay}"
4648
Width="24"
4749
Height="24"
4850
Margin="10,0"
@@ -74,6 +76,13 @@
7476
Visibility="{x:Bind ViewModel.ShowEnableSection, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}"
7577
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
7678

79+
<TextBlock
80+
Text="{x:Bind ViewModel.VpnStartProgressMessageOrDefault, Mode=OneWay}"
81+
TextWrapping="Wrap"
82+
Margin="0,6,0,6"
83+
Visibility="{x:Bind ViewModel.ShowVpnStartProgressSection, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}"
84+
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
85+
7786
<TextBlock
7887
Text="Workspaces"
7988
FontWeight="semibold"
@@ -344,7 +353,7 @@
344353
Command="{x:Bind ViewModel.ExitCommand, Mode=OneWay}"
345354
Margin="-12,-8,-12,-5"
346355
HorizontalAlignment="Stretch"
347-
HorizontalContentAlignment="Left">
356+
HorizontalContentAlignment="Left">
348357

349358
<TextBlockText="Exit"Foreground="{ThemeResource DefaultTextForegroundThemeBrush}" />
350359
</HyperlinkButton>

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp