Implement connection flow, test connection, and secret prompts
- Change ConnectSession/ConnectQuick to return SessionInfo (not string) - Change UpdateProfile to return (Profile, error) for frontend use - Make session connection asynchronous to avoid Wails event loop deadlock - Implement event-based secret prompt (backend ↔ frontend via requestID) - Add TestConnection API for TCP reachability checks with latency - Add 'Save & Connect' button in profile editor - Wire _testConnection to real backend TCP check - Add btn-accent CSS for the green action button
This commit is contained in:
@@ -50,6 +50,21 @@
|
||||
border-color: #3da69f;
|
||||
}
|
||||
|
||||
/* Accent — success-tinted action, e.g. "Save & Connect" */
|
||||
.btn-accent {
|
||||
background: var(--success);
|
||||
color: var(--text-inverse);
|
||||
border-color: var(--success);
|
||||
}
|
||||
.btn-accent:hover {
|
||||
background: #57a85c;
|
||||
border-color: #57a85c;
|
||||
}
|
||||
.btn-accent:active {
|
||||
background: #4a9a4e;
|
||||
border-color: #4a9a4e;
|
||||
}
|
||||
|
||||
/* Secondary — subtle border */
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
|
||||
@@ -185,12 +185,12 @@ class App {
|
||||
payload.message || 'Enter your password or passphrase.',
|
||||
);
|
||||
window.runtime.EventsEmit('session:secret:response', {
|
||||
sessionID: payload.sessionID,
|
||||
requestID: payload.requestID,
|
||||
value,
|
||||
});
|
||||
} catch (_) {
|
||||
window.runtime.EventsEmit('session:secret:response', {
|
||||
sessionID: payload.sessionID,
|
||||
requestID: payload.requestID,
|
||||
value: '',
|
||||
cancelled: true,
|
||||
});
|
||||
|
||||
@@ -163,16 +163,19 @@ class ProfileEditor {
|
||||
// ── Footer ──────────────────────────────────────────────
|
||||
const footer = createElement('div', 'drawer-footer');
|
||||
const cancelBtn = createElement('button', 'btn btn-secondary', { textContent: 'Cancel' });
|
||||
const testBtn = createElement('button', 'btn btn-outline', { textContent: 'Test Connection' });
|
||||
const testBtn = createElement('button', 'btn btn-outline', { textContent: 'Test' });
|
||||
const saveBtn = createElement('button', 'btn btn-primary', { textContent: isNew ? 'Create' : 'Save' });
|
||||
const connectBtn = createElement('button', 'btn btn-accent', { textContent: isNew ? 'Create & Connect' : 'Save & Connect' });
|
||||
|
||||
cancelBtn.addEventListener('click', () => this.close());
|
||||
testBtn.addEventListener('click', () => this._testConnection());
|
||||
saveBtn.addEventListener('click', () => this._handleSave());
|
||||
saveBtn.addEventListener('click', () => this._handleSave(false));
|
||||
connectBtn.addEventListener('click', () => this._handleSave(true));
|
||||
|
||||
footer.appendChild(cancelBtn);
|
||||
footer.appendChild(testBtn);
|
||||
footer.appendChild(saveBtn);
|
||||
footer.appendChild(connectBtn);
|
||||
drawer.appendChild(footer);
|
||||
|
||||
// ── Events ──────────────────────────────────────────────
|
||||
@@ -807,7 +810,7 @@ class ProfileEditor {
|
||||
// ── Actions ──────────────────────────────────────────────────────
|
||||
|
||||
/** @private */
|
||||
async _handleSave() {
|
||||
async _handleSave(andConnect = false) {
|
||||
this._collectFormValues();
|
||||
const errors = this._validate(this._profile);
|
||||
if (errors.length > 0) {
|
||||
@@ -816,17 +819,22 @@ class ProfileEditor {
|
||||
}
|
||||
|
||||
try {
|
||||
let saved;
|
||||
if (this._isNew) {
|
||||
const saved = await window.go.api.Service.CreateProfile(this._profile);
|
||||
saved = await window.go.api.Service.CreateProfile(this._profile);
|
||||
this.app.state.addProfile(saved);
|
||||
this.app.notifications.success('Profile created');
|
||||
} else {
|
||||
const saved = await window.go.api.Service.UpdateProfile(this._profile);
|
||||
saved = await window.go.api.Service.UpdateProfile(this._profile);
|
||||
this.app.state.updateProfile(saved);
|
||||
this.app.notifications.success('Profile updated');
|
||||
}
|
||||
this.app.sidebar.render();
|
||||
this.close();
|
||||
|
||||
if (andConnect && saved && saved.id) {
|
||||
this.app.connectProfile(saved.id);
|
||||
}
|
||||
} catch (err) {
|
||||
this.app.notifications.error('Save failed: ' + err);
|
||||
}
|
||||
@@ -841,7 +849,16 @@ class ProfileEditor {
|
||||
return;
|
||||
}
|
||||
|
||||
this.app.notifications.info('Test connection is not yet available.');
|
||||
const host = this._profile.host;
|
||||
const port = this._profile.port;
|
||||
this.app.notifications.info(`Testing connection to ${host}:${port}…`);
|
||||
|
||||
try {
|
||||
const result = await window.go.api.Service.TestConnection(host, port);
|
||||
this.app.notifications.success(`Reachable — ${result.latencyMs}ms latency`);
|
||||
} catch (err) {
|
||||
this.app.notifications.error('Test failed: ' + err);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Utilities ────────────────────────────────────────────────────
|
||||
|
||||
Vendored
+7
-3
@@ -7,9 +7,9 @@ export function ClearSessionHistory(arg1:string):Promise<void>;
|
||||
|
||||
export function CloseAllSessions():Promise<void>;
|
||||
|
||||
export function ConnectQuick(arg1:models.Profile):Promise<string>;
|
||||
export function ConnectQuick(arg1:models.Profile):Promise<models.SessionInfo>;
|
||||
|
||||
export function ConnectSession(arg1:string):Promise<string>;
|
||||
export function ConnectSession(arg1:string):Promise<models.SessionInfo>;
|
||||
|
||||
export function CreateProfile(arg1:models.Profile):Promise<models.Profile>;
|
||||
|
||||
@@ -65,6 +65,8 @@ export function PinSession(arg1:string,arg2:boolean):Promise<void>;
|
||||
|
||||
export function ReconnectSession(arg1:string):Promise<void>;
|
||||
|
||||
export function RegisterSecretResponseListener():Promise<void>;
|
||||
|
||||
export function RenameSession(arg1:string,arg2:string):Promise<void>;
|
||||
|
||||
export function ResizeSession(arg1:string,arg2:number,arg3:number):Promise<void>;
|
||||
@@ -87,9 +89,11 @@ export function SetFavourite(arg1:string,arg2:boolean):Promise<void>;
|
||||
|
||||
export function SetProfileSecret(arg1:string,arg2:string,arg3:string):Promise<void>;
|
||||
|
||||
export function TestConnection(arg1:string,arg2:number):Promise<Record<string, any>>;
|
||||
|
||||
export function UnlockSecretStore(arg1:string):Promise<void>;
|
||||
|
||||
export function UpdateProfile(arg1:models.Profile):Promise<void>;
|
||||
export function UpdateProfile(arg1:models.Profile):Promise<models.Profile>;
|
||||
|
||||
export function WriteSession(arg1:string,arg2:string):Promise<void>;
|
||||
|
||||
|
||||
@@ -126,6 +126,10 @@ export function ReconnectSession(arg1) {
|
||||
return window['go']['api']['Service']['ReconnectSession'](arg1);
|
||||
}
|
||||
|
||||
export function RegisterSecretResponseListener() {
|
||||
return window['go']['api']['Service']['RegisterSecretResponseListener']();
|
||||
}
|
||||
|
||||
export function RenameSession(arg1, arg2) {
|
||||
return window['go']['api']['Service']['RenameSession'](arg1, arg2);
|
||||
}
|
||||
@@ -170,6 +174,10 @@ export function SetProfileSecret(arg1, arg2, arg3) {
|
||||
return window['go']['api']['Service']['SetProfileSecret'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function TestConnection(arg1, arg2) {
|
||||
return window['go']['api']['Service']['TestConnection'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function UnlockSecretStore(arg1) {
|
||||
return window['go']['api']['Service']['UnlockSecretStore'](arg1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user