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]()
{
sessionState.processStarted();
emit stateChanged(sessionState.state());
});
connect(coreProcess, &CoreProcess::connectionEstablished, this, [this]()
{
sessionState.connectionEstablished();
emit stateChanged(sessionState.state());
});
connect(coreProcess, &CoreProcess::finished,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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