pinggy 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/chunk-65R2GMKQ.js +2101 -0
- package/dist/index.cjs +1798 -1349
- package/dist/index.d.cts +616 -0
- package/dist/index.d.ts +616 -0
- package/dist/index.js +24 -2
- package/dist/{main-K44C44NW.js → main-2QDG7PWL.js} +166 -1705
- package/package.json +2 -3
- package/.github/workflows/npm-publish-github-packages.yml +0 -34
- package/.github/workflows/publish-binaries.yml +0 -223
- package/Makefile +0 -4
- package/caxa_build.js +0 -24
- package/dist/chunk-T5ESYDJY.js +0 -121
- package/ent.plist +0 -14
- package/jest.config.js +0 -19
- package/src/_tests_/build_config.test.ts +0 -91
- package/src/cli/buildConfig.ts +0 -535
- package/src/cli/defaults.ts +0 -20
- package/src/cli/extendedOptions.ts +0 -153
- package/src/cli/help.ts +0 -43
- package/src/cli/options.ts +0 -50
- package/src/cli/starCli.ts +0 -229
- package/src/index.ts +0 -31
- package/src/logger.ts +0 -138
- package/src/main.ts +0 -87
- package/src/remote_management/handler.ts +0 -244
- package/src/remote_management/remoteManagement.ts +0 -226
- package/src/remote_management/remote_schema.ts +0 -176
- package/src/remote_management/websocket_handlers.ts +0 -180
- package/src/tui/blessed/TunnelTui.ts +0 -340
- package/src/tui/blessed/components/DisplayUpdaters.ts +0 -189
- package/src/tui/blessed/components/KeyBindings.ts +0 -236
- package/src/tui/blessed/components/Modals.ts +0 -302
- package/src/tui/blessed/components/UIComponents.ts +0 -306
- package/src/tui/blessed/components/index.ts +0 -4
- package/src/tui/blessed/config.ts +0 -53
- package/src/tui/blessed/headerFetcher.ts +0 -42
- package/src/tui/blessed/index.ts +0 -2
- package/src/tui/blessed/qrCodeGenerator.ts +0 -20
- package/src/tui/blessed/webDebuggerConnection.ts +0 -128
- package/src/tui/ink/asciArt.ts +0 -7
- package/src/tui/ink/hooks/useQrCodes.ts +0 -27
- package/src/tui/ink/hooks/useReqResHeaders.ts +0 -27
- package/src/tui/ink/hooks/useTerminalSize.ts +0 -26
- package/src/tui/ink/hooks/useTerminalStats.ts +0 -24
- package/src/tui/ink/hooks/useWebDebugger.ts +0 -98
- package/src/tui/ink/index.tsx +0 -243
- package/src/tui/ink/layout/Borders.tsx +0 -15
- package/src/tui/ink/layout/Container.tsx +0 -15
- package/src/tui/ink/sections/DebuggerDetailModal.tsx +0 -53
- package/src/tui/ink/sections/KeyBindings.tsx +0 -58
- package/src/tui/ink/sections/QrCodeSection.tsx +0 -28
- package/src/tui/ink/sections/StatsSection.tsx +0 -20
- package/src/tui/ink/sections/URLsSection.tsx +0 -53
- package/src/tui/ink/utils/utils.ts +0 -35
- package/src/tui/spinner/spinner.ts +0 -64
- package/src/tunnel_manager/TunnelManager.ts +0 -1212
- package/src/types.ts +0 -255
- package/src/utils/FileServer.ts +0 -112
- package/src/utils/detect_vc_redist_on_windows.ts +0 -111
- package/src/utils/getFreePort.ts +0 -41
- package/src/utils/htmlTemplates.ts +0 -146
- package/src/utils/parseArgs.ts +0 -79
- package/src/utils/printer.ts +0 -81
- package/src/utils/util.ts +0 -18
- package/src/workers/file_serve_worker.ts +0 -33
- package/tsconfig.json +0 -17
- package/tsup.config.ts +0 -12
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import blessed from "blessed";
|
|
2
|
-
import { ReqResPair, FinalConfig } from "../../../types.js";
|
|
3
|
-
import { ManagedTunnel, TunnelManager } from "../../../tunnel_manager/TunnelManager.js";
|
|
4
|
-
import { fetchReqResHeaders } from "../headerFetcher.js";
|
|
5
|
-
import { logger } from "../../../logger.js";
|
|
6
|
-
import { ModalManager, showDetailModal, closeDetailModal, showKeyBindingsModal, closeKeyBindingsModal, showLoadingModal, closeLoadingModal, showErrorModal } from "./Modals.js";
|
|
7
|
-
import { getTuiConfig } from "../config.js";
|
|
8
|
-
|
|
9
|
-
export interface KeyBindingsState {
|
|
10
|
-
currentQrIndex: number;
|
|
11
|
-
selectedIndex: number;
|
|
12
|
-
pairs: ReqResPair[];
|
|
13
|
-
urls: string[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface KeyBindingsCallbacks {
|
|
17
|
-
onQrIndexChange: (index: number) => void;
|
|
18
|
-
onSelectedIndexChange: (index: number, requestKey: number | null) => void;
|
|
19
|
-
onDestroy: () => void;
|
|
20
|
-
updateUrlsDisplay: () => void;
|
|
21
|
-
updateQrCodeDisplay: () => void;
|
|
22
|
-
updateRequestsDisplay: () => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Sets up all key bindings for the TUI
|
|
27
|
-
*/
|
|
28
|
-
export function setupKeyBindings(
|
|
29
|
-
screen: blessed.Widgets.Screen,
|
|
30
|
-
modalManager: ModalManager,
|
|
31
|
-
state: KeyBindingsState,
|
|
32
|
-
callbacks: KeyBindingsCallbacks,
|
|
33
|
-
tunnelConfig?: FinalConfig,
|
|
34
|
-
): void {
|
|
35
|
-
let inactivityTimeout: NodeJS.Timeout | null = null;
|
|
36
|
-
const { inactivityHttpSelectorTimeoutMs } = getTuiConfig();
|
|
37
|
-
const INACTIVITY_TIMEOUT_MS = inactivityHttpSelectorTimeoutMs;
|
|
38
|
-
|
|
39
|
-
// Function to reset inactivity timer
|
|
40
|
-
const resetInactivityTimer = () => {
|
|
41
|
-
if (inactivityTimeout) {
|
|
42
|
-
clearTimeout(inactivityTimeout);
|
|
43
|
-
}
|
|
44
|
-
// Only start timer if there's a selection
|
|
45
|
-
if (state.selectedIndex !== -1) {
|
|
46
|
-
inactivityTimeout = setTimeout(() => {
|
|
47
|
-
// Clear selection and reset viewport to top
|
|
48
|
-
callbacks.onSelectedIndexChange(-1, null);
|
|
49
|
-
callbacks.updateRequestsDisplay();
|
|
50
|
-
}, INACTIVITY_TIMEOUT_MS);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// Exit on Ctrl+C
|
|
55
|
-
screen.key(["C-c"], () => {
|
|
56
|
-
callbacks.onDestroy();
|
|
57
|
-
process.exit(0);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// Escape key
|
|
61
|
-
screen.key(["escape"], () => {
|
|
62
|
-
// Close loading modal and cancel fetch request
|
|
63
|
-
if (modalManager.loadingView) {
|
|
64
|
-
if (modalManager.fetchAbortController) {
|
|
65
|
-
modalManager.fetchAbortController.abort();
|
|
66
|
-
modalManager.fetchAbortController = null;
|
|
67
|
-
}
|
|
68
|
-
closeLoadingModal(screen, modalManager);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
if (modalManager.inDetailView) {
|
|
72
|
-
closeDetailModal(screen, modalManager);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
if (modalManager.keyBindingView) {
|
|
76
|
-
closeKeyBindingsModal(screen, modalManager);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
// Clear selection and reset viewport to top
|
|
80
|
-
if (state.selectedIndex !== -1) {
|
|
81
|
-
if (inactivityTimeout) {
|
|
82
|
-
clearTimeout(inactivityTimeout);
|
|
83
|
-
inactivityTimeout = null;
|
|
84
|
-
}
|
|
85
|
-
callbacks.onSelectedIndexChange(-1, null);
|
|
86
|
-
callbacks.updateRequestsDisplay();
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Navigation - Up
|
|
91
|
-
screen.key(["up"], () => {
|
|
92
|
-
if (modalManager.inDetailView || modalManager.keyBindingView || modalManager.loadingView) return;
|
|
93
|
-
resetInactivityTimer();
|
|
94
|
-
if (state.selectedIndex === -1) {
|
|
95
|
-
// No selection: select first item (latest request)
|
|
96
|
-
const requestKey = state.pairs[0]?.request?.key ?? null;
|
|
97
|
-
callbacks.onSelectedIndexChange(0, requestKey);
|
|
98
|
-
callbacks.updateRequestsDisplay();
|
|
99
|
-
resetInactivityTimer(); // Start timer after selection
|
|
100
|
-
} else if (state.selectedIndex > 0) {
|
|
101
|
-
const newIndex = state.selectedIndex - 1;
|
|
102
|
-
const requestKey = state.pairs[newIndex]?.request?.key ?? null;
|
|
103
|
-
callbacks.onSelectedIndexChange(newIndex, requestKey);
|
|
104
|
-
callbacks.updateRequestsDisplay();
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// Navigation - Down
|
|
109
|
-
screen.key(["down"], () => {
|
|
110
|
-
if (modalManager.inDetailView || modalManager.keyBindingView || modalManager.loadingView) return;
|
|
111
|
-
resetInactivityTimer();
|
|
112
|
-
const config = getTuiConfig();
|
|
113
|
-
// Limit to maxRequestPairs for navigation bounds
|
|
114
|
-
const limitedLength = Math.min(state.pairs.length, config.maxRequestPairs);
|
|
115
|
-
if (state.selectedIndex === -1) {
|
|
116
|
-
// No selection: select first item (latest request)
|
|
117
|
-
if (limitedLength > 0) {
|
|
118
|
-
const requestKey = state.pairs[0]?.request?.key ?? null;
|
|
119
|
-
callbacks.onSelectedIndexChange(0, requestKey);
|
|
120
|
-
callbacks.updateRequestsDisplay();
|
|
121
|
-
resetInactivityTimer(); // Start timer after selection
|
|
122
|
-
}
|
|
123
|
-
} else if (state.selectedIndex < limitedLength - 1) {
|
|
124
|
-
const newIndex = state.selectedIndex + 1;
|
|
125
|
-
const requestKey = state.pairs[newIndex]?.request?.key ?? null;
|
|
126
|
-
callbacks.onSelectedIndexChange(newIndex, requestKey);
|
|
127
|
-
callbacks.updateRequestsDisplay();
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// End - Jump to last item
|
|
132
|
-
screen.key(["end"], () => {
|
|
133
|
-
if (modalManager.inDetailView || modalManager.keyBindingView || modalManager.loadingView) return;
|
|
134
|
-
resetInactivityTimer();
|
|
135
|
-
const config = getTuiConfig();
|
|
136
|
-
const limitedLength = Math.min(state.pairs.length, config.maxRequestPairs);
|
|
137
|
-
const lastIndex = Math.max(0, limitedLength - 1);
|
|
138
|
-
if (state.selectedIndex !== lastIndex) {
|
|
139
|
-
const requestKey = state.pairs[lastIndex]?.request?.key ?? null;
|
|
140
|
-
callbacks.onSelectedIndexChange(lastIndex, requestKey);
|
|
141
|
-
callbacks.updateRequestsDisplay();
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// Enter to view details
|
|
146
|
-
screen.key(["enter"], async () => {
|
|
147
|
-
if (modalManager.inDetailView || modalManager.keyBindingView || modalManager.loadingView) return;
|
|
148
|
-
// Only work when there's a selection
|
|
149
|
-
if (state.selectedIndex === -1) return;
|
|
150
|
-
|
|
151
|
-
resetInactivityTimer();
|
|
152
|
-
const pair = state.pairs[state.selectedIndex];
|
|
153
|
-
if (pair?.request?.key !== undefined && pair?.request?.key !== null) {
|
|
154
|
-
// Create AbortController for this fetch request
|
|
155
|
-
const abortController = new AbortController();
|
|
156
|
-
modalManager.fetchAbortController = abortController;
|
|
157
|
-
|
|
158
|
-
showLoadingModal(screen, modalManager, "Fetching request details...");
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
const headers = await fetchReqResHeaders(
|
|
162
|
-
tunnelConfig?.webDebugger || "",
|
|
163
|
-
pair.request.key,
|
|
164
|
-
abortController.signal
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
// Check if request was aborted
|
|
168
|
-
if (abortController.signal.aborted) {
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Close loading and show details
|
|
173
|
-
closeLoadingModal(screen, modalManager);
|
|
174
|
-
modalManager.fetchAbortController = null;
|
|
175
|
-
showDetailModal(screen, modalManager, headers.req, headers.res);
|
|
176
|
-
} catch (err: any) {
|
|
177
|
-
// Don't show error if request was cancelled by user
|
|
178
|
-
if (err?.name === 'AbortError' || abortController.signal.aborted) {
|
|
179
|
-
logger.info("Fetch request cancelled by user");
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Close loading and show error modal
|
|
184
|
-
closeLoadingModal(screen, modalManager);
|
|
185
|
-
modalManager.fetchAbortController = null;
|
|
186
|
-
|
|
187
|
-
const errorMessage = err?.message || String(err) || "Unknown error occurred";
|
|
188
|
-
logger.error("Fetch error:", err);
|
|
189
|
-
showErrorModal(screen, modalManager, "Failed to fetch request details", errorMessage);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// Help toggle
|
|
195
|
-
screen.key(["h"], () => {
|
|
196
|
-
if (modalManager.inDetailView || modalManager.loadingView) return;
|
|
197
|
-
if (modalManager.keyBindingView) {
|
|
198
|
-
closeKeyBindingsModal(screen, modalManager);
|
|
199
|
-
} else {
|
|
200
|
-
showKeyBindingsModal(screen, modalManager);
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// Copy URL
|
|
205
|
-
screen.key(["c"], async () => {
|
|
206
|
-
if (modalManager.inDetailView || modalManager.keyBindingView || modalManager.loadingView) return;
|
|
207
|
-
if (state.urls.length > 0) {
|
|
208
|
-
try {
|
|
209
|
-
const clipboardy = await import("clipboardy");
|
|
210
|
-
clipboardy.default.writeSync(state.urls[state.currentQrIndex]);
|
|
211
|
-
} catch (err) {
|
|
212
|
-
logger.error("Failed to copy to clipboard:", err);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// QR code navigation - Left
|
|
218
|
-
screen.key(["left"], () => {
|
|
219
|
-
if (modalManager.inDetailView || modalManager.keyBindingView || modalManager.loadingView) return;
|
|
220
|
-
if (state.currentQrIndex > 0) {
|
|
221
|
-
callbacks.onQrIndexChange(state.currentQrIndex - 1);
|
|
222
|
-
callbacks.updateUrlsDisplay();
|
|
223
|
-
callbacks.updateQrCodeDisplay();
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
// QR code navigation - Right
|
|
228
|
-
screen.key(["right"], () => {
|
|
229
|
-
if (modalManager.inDetailView || modalManager.keyBindingView || modalManager.loadingView) return;
|
|
230
|
-
if (state.currentQrIndex < state.urls.length - 1) {
|
|
231
|
-
callbacks.onQrIndexChange(state.currentQrIndex + 1);
|
|
232
|
-
callbacks.updateUrlsDisplay();
|
|
233
|
-
callbacks.updateQrCodeDisplay();
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
}
|
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
import blessed from "blessed";
|
|
2
|
-
|
|
3
|
-
export interface ModalManager {
|
|
4
|
-
detailModal: blessed.Widgets.BoxElement | null;
|
|
5
|
-
keyBindingsModal: blessed.Widgets.BoxElement | null;
|
|
6
|
-
disconnectModal: blessed.Widgets.BoxElement | null;
|
|
7
|
-
inDetailView: boolean;
|
|
8
|
-
keyBindingView: boolean;
|
|
9
|
-
inDisconnectView: boolean;
|
|
10
|
-
loadingBox: blessed.Widgets.BoxElement | null;
|
|
11
|
-
loadingView: boolean;
|
|
12
|
-
fetchAbortController: AbortController | null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Shows the detail modal with request/response data
|
|
17
|
-
*/
|
|
18
|
-
export function showDetailModal(
|
|
19
|
-
screen: blessed.Widgets.Screen,
|
|
20
|
-
manager: ModalManager,
|
|
21
|
-
requestText?: string,
|
|
22
|
-
responseText?: string
|
|
23
|
-
): void {
|
|
24
|
-
manager.inDetailView = true;
|
|
25
|
-
|
|
26
|
-
manager.detailModal = blessed.box({
|
|
27
|
-
parent: screen,
|
|
28
|
-
top: "center",
|
|
29
|
-
left: "center",
|
|
30
|
-
width: "90%",
|
|
31
|
-
height: "90%",
|
|
32
|
-
border: {
|
|
33
|
-
type: "line",
|
|
34
|
-
},
|
|
35
|
-
style: {
|
|
36
|
-
border: {
|
|
37
|
-
fg: "green",
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
padding: { left: 2, right: 2, top: 1, bottom: 1 },
|
|
41
|
-
tags: true,
|
|
42
|
-
scrollable: true,
|
|
43
|
-
keys: true,
|
|
44
|
-
vi: true,
|
|
45
|
-
alwaysScroll: true,
|
|
46
|
-
scrollbar: {
|
|
47
|
-
ch: " ",
|
|
48
|
-
track: {
|
|
49
|
-
bg: "cyan",
|
|
50
|
-
},
|
|
51
|
-
style: {
|
|
52
|
-
inverse: true,
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const content = `{cyan-fg}{bold}Request{/bold}{/cyan-fg}
|
|
58
|
-
${requestText || "(no request data)"}
|
|
59
|
-
|
|
60
|
-
{magenta-fg}{bold}Response{/bold}{/magenta-fg}
|
|
61
|
-
${responseText || "(no response data)"}
|
|
62
|
-
|
|
63
|
-
{white-bg}{black-fg}Press ESC to close{/black-fg}{/white-bg}`;
|
|
64
|
-
|
|
65
|
-
manager.detailModal.setContent(content);
|
|
66
|
-
manager.detailModal.focus();
|
|
67
|
-
screen.render();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Closes the detail modal
|
|
72
|
-
*/
|
|
73
|
-
export function closeDetailModal(
|
|
74
|
-
screen: blessed.Widgets.Screen,
|
|
75
|
-
manager: ModalManager
|
|
76
|
-
): void {
|
|
77
|
-
if (manager.detailModal) {
|
|
78
|
-
manager.detailModal.destroy();
|
|
79
|
-
manager.detailModal = null;
|
|
80
|
-
}
|
|
81
|
-
manager.inDetailView = false;
|
|
82
|
-
screen.render();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Shows the key bindings modal
|
|
87
|
-
*/
|
|
88
|
-
export function showKeyBindingsModal(
|
|
89
|
-
screen: blessed.Widgets.Screen,
|
|
90
|
-
manager: ModalManager
|
|
91
|
-
): void {
|
|
92
|
-
manager.keyBindingView = true;
|
|
93
|
-
|
|
94
|
-
manager.keyBindingsModal = blessed.box({
|
|
95
|
-
parent: screen,
|
|
96
|
-
top: "center",
|
|
97
|
-
left: "center",
|
|
98
|
-
width: "60%",
|
|
99
|
-
height: "80%",
|
|
100
|
-
border: {
|
|
101
|
-
type: "line",
|
|
102
|
-
},
|
|
103
|
-
style: {
|
|
104
|
-
border: {
|
|
105
|
-
fg: "green",
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
padding: { left: 2, right: 2, top: 1, bottom: 1 },
|
|
109
|
-
tags: true,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const content = `{cyan-fg}{bold}Key Bindings{/bold}{/cyan-fg}
|
|
113
|
-
|
|
114
|
-
{bold}h{/bold} This page
|
|
115
|
-
{bold}c{/bold} Copy the selected URL to clipboard
|
|
116
|
-
{bold}Ctrl+c{/bold} Exit
|
|
117
|
-
|
|
118
|
-
Enter/Return Open selected request
|
|
119
|
-
Esc Return to main page (or close modals)
|
|
120
|
-
UP (↑) Scroll up the requests
|
|
121
|
-
Down (↓) Scroll down the requests
|
|
122
|
-
Left (←) Show qr code for previous url
|
|
123
|
-
Right (→) Show qr code for next url
|
|
124
|
-
Home Jump to top of requests
|
|
125
|
-
End Jump to bottom of requests
|
|
126
|
-
Ctrl+c Force Exit
|
|
127
|
-
|
|
128
|
-
{white-bg}{black-fg}Press ESC to close{/black-fg}{/white-bg}`;
|
|
129
|
-
|
|
130
|
-
manager.keyBindingsModal.setContent(content);
|
|
131
|
-
manager.keyBindingsModal.focus();
|
|
132
|
-
screen.render();
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Closes the key bindings modal
|
|
137
|
-
*/
|
|
138
|
-
export function closeKeyBindingsModal(
|
|
139
|
-
screen: blessed.Widgets.Screen,
|
|
140
|
-
manager: ModalManager
|
|
141
|
-
): void {
|
|
142
|
-
if (manager.keyBindingsModal) {
|
|
143
|
-
manager.keyBindingsModal.destroy();
|
|
144
|
-
manager.keyBindingsModal = null;
|
|
145
|
-
}
|
|
146
|
-
manager.keyBindingView = false;
|
|
147
|
-
screen.render();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Shows the disconnect modal
|
|
152
|
-
*/
|
|
153
|
-
export function showDisconnectModal(
|
|
154
|
-
screen: blessed.Widgets.Screen,
|
|
155
|
-
manager: ModalManager,
|
|
156
|
-
message?: string,
|
|
157
|
-
onClose?: () => void
|
|
158
|
-
): void {
|
|
159
|
-
manager.inDisconnectView = true;
|
|
160
|
-
|
|
161
|
-
manager.disconnectModal = blessed.box({
|
|
162
|
-
parent: screen,
|
|
163
|
-
top: "center",
|
|
164
|
-
left: "center",
|
|
165
|
-
width: "50%",
|
|
166
|
-
height: "20%",
|
|
167
|
-
border: {
|
|
168
|
-
type: "line",
|
|
169
|
-
},
|
|
170
|
-
style: {
|
|
171
|
-
border: {
|
|
172
|
-
fg: "red",
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
padding: { left: 2, right: 2, top: 1, bottom: 1 },
|
|
176
|
-
tags: true,
|
|
177
|
-
align: "center",
|
|
178
|
-
valign: "middle",
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const content = `{red-fg}{bold}Tunnel Disconnected{/bold}{/red-fg}
|
|
182
|
-
|
|
183
|
-
${message || "Disconnect request received. Tunnel will be closed."}
|
|
184
|
-
|
|
185
|
-
{white-bg}{black-fg}Closing in 3 seconds... {/black-fg}{/white-bg}`;
|
|
186
|
-
|
|
187
|
-
manager.disconnectModal.setContent(content);
|
|
188
|
-
manager.disconnectModal.focus();
|
|
189
|
-
screen.render();
|
|
190
|
-
|
|
191
|
-
// Auto-close after 5 seconds
|
|
192
|
-
const timeout = setTimeout(() => {
|
|
193
|
-
closeDisconnectModal(screen, manager);
|
|
194
|
-
if (onClose) onClose();
|
|
195
|
-
}, 5000);
|
|
196
|
-
|
|
197
|
-
// Allow manual close with any key
|
|
198
|
-
const keyHandler = () => {
|
|
199
|
-
clearTimeout(timeout);
|
|
200
|
-
closeDisconnectModal(screen, manager);
|
|
201
|
-
if (onClose) onClose();
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
manager.disconnectModal.key(['escape', 'enter', 'space'], keyHandler);
|
|
205
|
-
screen.key(['escape', 'enter', 'space'], keyHandler);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Closes the disconnect modal
|
|
210
|
-
*/
|
|
211
|
-
export function closeDisconnectModal(
|
|
212
|
-
screen: blessed.Widgets.Screen,
|
|
213
|
-
manager: ModalManager
|
|
214
|
-
): void {
|
|
215
|
-
if (manager.disconnectModal) {
|
|
216
|
-
manager.disconnectModal.destroy();
|
|
217
|
-
manager.disconnectModal = null;
|
|
218
|
-
}
|
|
219
|
-
manager.inDisconnectView = false;
|
|
220
|
-
screen.render();
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export function showLoadingModal(
|
|
224
|
-
screen: blessed.Widgets.Screen,
|
|
225
|
-
modalManager: ModalManager,
|
|
226
|
-
message: string = "Loading..."
|
|
227
|
-
): void {
|
|
228
|
-
if (modalManager.loadingView) return;
|
|
229
|
-
|
|
230
|
-
modalManager.loadingBox = blessed.box({
|
|
231
|
-
parent: screen,
|
|
232
|
-
top: "center",
|
|
233
|
-
left: "center",
|
|
234
|
-
width: "60%",
|
|
235
|
-
height: 8,
|
|
236
|
-
border: { type: "line" },
|
|
237
|
-
style: {
|
|
238
|
-
border: { fg: "yellow" },
|
|
239
|
-
},
|
|
240
|
-
tags: true,
|
|
241
|
-
content: `{center}{yellow-fg}{bold}${message}{/bold}{/yellow-fg}
|
|
242
|
-
|
|
243
|
-
{gray-fg}Press ESC to cancel{/gray-fg}{/center}`,
|
|
244
|
-
valign: "middle",
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
modalManager.loadingView = true;
|
|
248
|
-
screen.render();
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Closes the loading modal
|
|
253
|
-
*/
|
|
254
|
-
export function closeLoadingModal(
|
|
255
|
-
screen: blessed.Widgets.Screen,
|
|
256
|
-
modalManager: ModalManager
|
|
257
|
-
): void {
|
|
258
|
-
if (!modalManager.loadingView || !modalManager.loadingBox) return;
|
|
259
|
-
|
|
260
|
-
modalManager.loadingBox.destroy();
|
|
261
|
-
modalManager.loadingBox = null;
|
|
262
|
-
modalManager.loadingView = false;
|
|
263
|
-
screen.render();
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Shows an error modal with a message
|
|
268
|
-
*/
|
|
269
|
-
export function showErrorModal(
|
|
270
|
-
screen: blessed.Widgets.Screen,
|
|
271
|
-
modalManager: ModalManager,
|
|
272
|
-
title: string = "Error",
|
|
273
|
-
message: string
|
|
274
|
-
): void {
|
|
275
|
-
// Reuse the loading box for error display
|
|
276
|
-
if (modalManager.loadingBox) {
|
|
277
|
-
modalManager.loadingBox.destroy();
|
|
278
|
-
modalManager.loadingBox = null;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
modalManager.loadingBox = blessed.box({
|
|
282
|
-
parent: screen,
|
|
283
|
-
top: "center",
|
|
284
|
-
left: "center",
|
|
285
|
-
width: "60%",
|
|
286
|
-
height: 9,
|
|
287
|
-
border: { type: "line" },
|
|
288
|
-
style: {
|
|
289
|
-
border: { fg: "red" },
|
|
290
|
-
},
|
|
291
|
-
tags: true,
|
|
292
|
-
content: `{center}{red-fg}{bold}${title}{/bold}{/red-fg}
|
|
293
|
-
|
|
294
|
-
{white-fg}${message}{/white-fg}
|
|
295
|
-
|
|
296
|
-
{gray-fg}Press ESC to close{/gray-fg}{/center}`,
|
|
297
|
-
valign: "middle",
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
modalManager.loadingView = true;
|
|
301
|
-
screen.render();
|
|
302
|
-
}
|