feat: 改进的状态指示

This commit is contained in:
Chenx Dust
2026-08-10 18:58:51 +08:00
parent 7303f18959
commit 62229ad781
14 changed files with 93 additions and 14 deletions

View File

@@ -21,7 +21,11 @@ ConnectionSession::ConnectionSession(CoreProcess *coreProcess, QObject *parent)
}); });
connect(coreProcess, &CoreProcess::started, this, [this]() connect(coreProcess, &CoreProcess::started, this, [this]()
{ {
sessionState.processStarted(); emit stateChanged(sessionState.state());
});
connect(coreProcess, &CoreProcess::connectionEstablished, this, [this]()
{
sessionState.connectionEstablished();
emit stateChanged(sessionState.state()); emit stateChanged(sessionState.state());
}); });
connect(coreProcess, &CoreProcess::finished, connect(coreProcess, &CoreProcess::finished,

View File

@@ -30,6 +30,7 @@ signals:
void ssoAuth(); void ssoAuth();
void askSudoPass(); void askSudoPass();
void started(); void started();
void connectionEstablished();
void finished(); void finished();
}; };

View File

@@ -42,7 +42,7 @@ bool ConnectionSessionState::requestStart(const ReconnectPolicy &policy)
return true; return true;
} }
void ConnectionSessionState::processStarted() void ConnectionSessionState::connectionEstablished()
{ {
if (currentState == ConnectionState::Starting) if (currentState == ConnectionState::Starting)
{ {
@@ -76,6 +76,7 @@ bool ConnectionSessionState::requestStop()
ProcessFinishAction ConnectionSessionState::processFinished() ProcessFinishAction ConnectionSessionState::processFinished()
{ {
const bool connectionWasEstablished = currentState == ConnectionState::Running;
if (desiredConnected if (desiredConnected
&& reconnectPolicy.enabled && reconnectPolicy.enabled
&& isReconnectable(currentError)) && isReconnectable(currentError))
@@ -85,9 +86,16 @@ ProcessFinishAction ConnectionSessionState::processFinished()
} }
desiredConnected = false; desiredConnected = false;
currentState = currentError == ZJU_ERROR::NONE if (connectionWasEstablished)
? ConnectionState::Disconnected {
: ConnectionState::Failed; currentState = ConnectionState::Interrupted;
}
else
{
currentState = currentError == ZJU_ERROR::NONE
? ConnectionState::Disconnected
: ConnectionState::Failed;
}
return ProcessFinishAction::Complete; return ProcessFinishAction::Complete;
} }

View File

@@ -10,6 +10,7 @@ enum class ConnectionState
Running, Running,
Stopping, Stopping,
Reconnecting, Reconnecting,
Interrupted,
Failed, Failed,
}; };
@@ -35,7 +36,7 @@ public:
int reconnectDelayMs() const; int reconnectDelayMs() const;
bool requestStart(const ReconnectPolicy &policy); bool requestStart(const ReconnectPolicy &policy);
void processStarted(); void connectionEstablished();
void recordError(ZJU_ERROR error); void recordError(ZJU_ERROR error);
bool requestStop(); bool requestStop();
ProcessFinishAction processFinished(); ProcessFinishAction processFinished();

View File

@@ -26,6 +26,10 @@ CoreOutputEvent CoreOutputParser::parse(const QString &output)
{ {
return CoreOutputEvent::SsoCallback; return CoreOutputEvent::SsoCallback;
} }
if (output.contains("VPN client started"))
{
return CoreOutputEvent::ClientStarted;
}
if (output.contains("graph check code still required after second login attempt") || if (output.contains("graph check code still required after second login attempt") ||
output.contains("The characters are incorrect or has expired!")) output.contains("The characters are incorrect or has expired!"))
{ {

View File

@@ -13,6 +13,7 @@ enum class CoreOutputEvent
SmsCode, SmsCode,
TotpCode, TotpCode,
SsoCallback, SsoCallback,
ClientStarted,
CaptchaFailed, CaptchaFailed,
AccessDenied, AccessDenied,
ListenFailed, ListenFailed,

View File

@@ -104,6 +104,9 @@ void ZjuConnectProcess::processOutputLines(const QList<QByteArray> &lines)
case CoreOutputEvent::SsoCallback: case CoreOutputEvent::SsoCallback:
emit ssoAuth(); emit ssoAuth();
break; break;
case CoreOutputEvent::ClientStarted:
emit connectionEstablished();
break;
case CoreOutputEvent::CaptchaFailed: case CoreOutputEvent::CaptchaFailed:
emit error(ZJU_ERROR::CAPTCHA_FAILED); emit error(ZJU_ERROR::CAPTCHA_FAILED);
break; break;

View File

@@ -133,7 +133,10 @@ ConnectionUiController::ConnectionUiController(
this->connectButton->setText("连接服务器"); this->connectButton->setText("连接服务器");
this->trayConnectAction->setText("连接服务器"); this->trayConnectAction->setText("连接服务器");
this->proxyButton->hide(); this->proxyButton->hide();
showConnectionError(error); if (this->connectionSession->state() != ConnectionState::Interrupted)
{
showConnectionError(error);
}
} }
); );
connect( connect(

View File

@@ -482,6 +482,11 @@ void MainWindow::updateConnectionState(ConnectionState state)
title = "正在重连"; title = "正在重连";
detail = "连接中断,正在按当前策略重新尝试。"; detail = "连接中断,正在按当前策略重新尝试。";
break; break;
case ConnectionState::Interrupted:
propertyValue = "failed";
title = "连接已断开";
detail = "VPN 核心意外退出,请查看右侧日志。";
break;
case ConnectionState::Failed: case ConnectionState::Failed:
propertyValue = "failed"; propertyValue = "failed";
title = "连接失败"; title = "连接失败";

View File

@@ -76,7 +76,7 @@ QMenu::item:selected {
} }
#statusIndicator[connectionState="running"] { #statusIndicator[connectionState="running"] {
background: #55b9e7; background: #5acb83;
} }
#statusIndicator[connectionState="stopping"] { #statusIndicator[connectionState="stopping"] {

View File

@@ -76,7 +76,7 @@ QMenu::item:selected {
} }
#statusIndicator[connectionState="running"] { #statusIndicator[connectionState="running"] {
background: #278bb9; background: #2e8b57;
} }
#statusIndicator[connectionState="stopping"] { #statusIndicator[connectionState="stopping"] {

View File

@@ -33,6 +33,11 @@ public:
emit finished(); emit finished();
} }
void establishConnection()
{
emit connectionEstablished();
}
void requestSudoPassword() void requestSudoPassword()
{ {
emit askSudoPass(); emit askSudoPass();
@@ -53,7 +58,7 @@ bool delegatesProcessLifecycleThroughPort()
if (!session.start(profile, {}) if (!session.start(profile, {})
|| coreProcess->startCalls != 1 || coreProcess->startCalls != 1
|| session.state() != ConnectionState::Running || session.state() != ConnectionState::Starting
|| !session.isActive() || !session.isActive()
|| session.start(profile, {})) || session.start(profile, {}))
{ {
@@ -61,6 +66,13 @@ bool delegatesProcessLifecycleThroughPort()
return false; return false;
} }
coreProcess->establishConnection();
if (session.state() != ConnectionState::Running)
{
qCritical() << "connection establishment was not reflected";
return false;
}
session.submitInput("input"); session.submitInput("input");
session.stop(); session.stop();
if (coreProcess->lastInput != "input" if (coreProcess->lastInput != "input"
@@ -82,6 +94,25 @@ bool delegatesProcessLifecycleThroughPort()
return true; return true;
} }
bool distinguishesInterruptedConnectionFromStartupFailure()
{
auto *coreProcess = new FakeCoreProcess();
ConnectionSession session(coreProcess);
ConnectionProfile profile;
session.start(profile, {});
coreProcess->establishConnection();
emit coreProcess->error(ZJU_ERROR::OTHER);
coreProcess->complete();
if (session.state() != ConnectionState::Interrupted)
{
qCritical() << "established connection was reported as startup failure";
return false;
}
return true;
}
bool emptySudoPasswordStopsTheSession() bool emptySudoPasswordStopsTheSession()
{ {
auto *coreProcess = new FakeCoreProcess(); auto *coreProcess = new FakeCoreProcess();
@@ -133,6 +164,7 @@ int main(int argc, char *argv[])
{ {
QCoreApplication application(argc, argv); QCoreApplication application(argc, argv);
return delegatesProcessLifecycleThroughPort() return delegatesProcessLifecycleThroughPort()
&& distinguishesInterruptedConnectionFromStartupFailure()
&& emptySudoPasswordStopsTheSession() && emptySudoPasswordStopsTheSession()
&& cancelledInteractiveInputSubmitsNewlineBeforeStopping() ? 0 : 1; && cancelledInteractiveInputSubmitsNewlineBeforeStopping() ? 0 : 1;
} }

View File

@@ -16,7 +16,7 @@ bool normalLifecycle()
return false; return false;
} }
session.processStarted(); session.connectionEstablished();
if (session.state() != ConnectionState::Running) if (session.state() != ConnectionState::Running)
{ {
qCritical() << "normalLifecycle failed at running"; qCritical() << "normalLifecycle failed at running";
@@ -43,7 +43,7 @@ bool reconnectsOnlyEligibleFailures()
{ {
ConnectionSessionState session; ConnectionSessionState session;
session.requestStart({true, 2500}); session.requestStart({true, 2500});
session.processStarted(); session.connectionEstablished();
session.recordError(ZJU_ERROR::AUTH_EXPIRED); session.recordError(ZJU_ERROR::AUTH_EXPIRED);
if (session.processFinished() != ProcessFinishAction::Reconnect if (session.processFinished() != ProcessFinishAction::Reconnect
@@ -61,7 +61,6 @@ bool reconnectsOnlyEligibleFailures()
return false; return false;
} }
session.processStarted();
session.recordError(ZJU_ERROR::INVALID_DETAIL); session.recordError(ZJU_ERROR::INVALID_DETAIL);
if (session.processFinished() != ProcessFinishAction::Complete if (session.processFinished() != ProcessFinishAction::Complete
|| session.state() != ConnectionState::Failed) || session.state() != ConnectionState::Failed)
@@ -76,7 +75,7 @@ bool keepsFirstErrorAndCancelsPendingReconnect()
{ {
ConnectionSessionState session; ConnectionSessionState session;
session.requestStart({true, 1000}); session.requestStart({true, 1000});
session.processStarted(); session.connectionEstablished();
session.recordError(ZJU_ERROR::AUTH_EXPIRED); session.recordError(ZJU_ERROR::AUTH_EXPIRED);
session.recordError(ZJU_ERROR::OTHER); session.recordError(ZJU_ERROR::OTHER);
if (session.error() != ZJU_ERROR::AUTH_EXPIRED) if (session.error() != ZJU_ERROR::AUTH_EXPIRED)
@@ -95,6 +94,22 @@ bool keepsFirstErrorAndCancelsPendingReconnect()
} }
return true; return true;
} }
bool establishedConnectionEndsAsInterrupted()
{
ConnectionSessionState session;
session.requestStart({false, 1000});
session.connectionEstablished();
session.recordError(ZJU_ERROR::OTHER);
if (session.processFinished() != ProcessFinishAction::Complete
|| session.state() != ConnectionState::Interrupted)
{
qCritical() << "establishedConnectionEndsAsInterrupted failed";
return false;
}
return true;
}
} }
int main(int argc, char *argv[]) int main(int argc, char *argv[])
@@ -103,6 +118,7 @@ int main(int argc, char *argv[])
return normalLifecycle() return normalLifecycle()
&& reconnectsOnlyEligibleFailures() && reconnectsOnlyEligibleFailures()
&& keepsFirstErrorAndCancelsPendingReconnect() && keepsFirstErrorAndCancelsPendingReconnect()
&& establishedConnectionEndsAsInterrupted()
? 0 ? 0
: 1; : 1;
} }

View File

@@ -21,6 +21,7 @@ bool recognizesCoreProtocol()
{"Please enter your SMS code:", CoreOutputEvent::SmsCode}, {"Please enter your SMS code:", CoreOutputEvent::SmsCode},
{"Please enter your TOTP code:", CoreOutputEvent::TotpCode}, {"Please enter your TOTP code:", CoreOutputEvent::TotpCode},
{"Please enter the callback url:", CoreOutputEvent::SsoCallback}, {"Please enter the callback url:", CoreOutputEvent::SsoCallback},
{"VPN client started", CoreOutputEvent::ClientStarted},
{"graph check code still required after second login attempt", CoreOutputEvent::CaptchaFailed}, {"graph check code still required after second login attempt", CoreOutputEvent::CaptchaFailed},
{"Access is denied.", CoreOutputEvent::AccessDenied}, {"Access is denied.", CoreOutputEvent::AccessDenied},
{"listen failed on 127.0.0.1", CoreOutputEvent::ListenFailed}, {"listen failed on 127.0.0.1", CoreOutputEvent::ListenFailed},