x-shell.js 1.0.0 → 1.1.0
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/CHANGELOG.md +45 -0
- package/README.md +292 -2
- package/dist/client/browser-bundle.js +197 -3
- package/dist/client/browser-bundle.js.map +2 -2
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/terminal-client.d.ts +59 -3
- package/dist/client/terminal-client.d.ts.map +1 -1
- package/dist/client/terminal-client.js +194 -4
- package/dist/client/terminal-client.js.map +1 -1
- package/dist/server/circular-buffer.d.ts +55 -0
- package/dist/server/circular-buffer.d.ts.map +1 -0
- package/dist/server/circular-buffer.js +91 -0
- package/dist/server/circular-buffer.js.map +1 -0
- package/dist/server/index.d.ts +4 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/session-manager.d.ts +195 -0
- package/dist/server/session-manager.d.ts.map +1 -0
- package/dist/server/session-manager.js +448 -0
- package/dist/server/session-manager.js.map +1 -0
- package/dist/server/terminal-server.d.ts +54 -6
- package/dist/server/terminal-server.d.ts.map +1 -1
- package/dist/server/terminal-server.js +313 -80
- package/dist/server/terminal-server.js.map +1 -1
- package/dist/shared/types.d.ts +122 -2
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/ui/browser-bundle.js +745 -47
- package/dist/ui/browser-bundle.js.map +3 -3
- package/dist/ui/x-shell-terminal.d.ts +99 -1
- package/dist/ui/x-shell-terminal.d.ts.map +1 -1
- package/dist/ui/x-shell-terminal.js +604 -48
- package/dist/ui/x-shell-terminal.js.map +1 -1
- package/package.json +5 -2
|
@@ -791,9 +791,19 @@ var TerminalClient = class {
|
|
|
791
791
|
this.spawnedHandlers = [];
|
|
792
792
|
this.serverInfoHandlers = [];
|
|
793
793
|
this.containerListHandlers = [];
|
|
794
|
-
//
|
|
794
|
+
// Session multiplexing handlers
|
|
795
|
+
this.sessionListHandlers = [];
|
|
796
|
+
this.joinedHandlers = [];
|
|
797
|
+
this.leftHandlers = [];
|
|
798
|
+
this.clientJoinedHandlers = [];
|
|
799
|
+
this.clientLeftHandlers = [];
|
|
800
|
+
this.sessionClosedHandlers = [];
|
|
801
|
+
// Promise resolvers for spawn/join
|
|
795
802
|
this.spawnResolve = null;
|
|
796
803
|
this.spawnReject = null;
|
|
804
|
+
this.joinResolve = null;
|
|
805
|
+
this.joinReject = null;
|
|
806
|
+
this.listSessionsResolve = null;
|
|
797
807
|
this.config = {
|
|
798
808
|
url: config.url,
|
|
799
809
|
reconnect: config.reconnect ?? true,
|
|
@@ -927,6 +937,11 @@ var TerminalClient = class {
|
|
|
927
937
|
this.spawnResolve = null;
|
|
928
938
|
this.spawnReject = null;
|
|
929
939
|
}
|
|
940
|
+
if (this.joinReject) {
|
|
941
|
+
this.joinReject(error);
|
|
942
|
+
this.joinResolve = null;
|
|
943
|
+
this.joinReject = null;
|
|
944
|
+
}
|
|
930
945
|
break;
|
|
931
946
|
case "serverInfo":
|
|
932
947
|
this.serverInfo = message.info;
|
|
@@ -935,6 +950,64 @@ var TerminalClient = class {
|
|
|
935
950
|
case "containerList":
|
|
936
951
|
this.containerListHandlers.forEach((handler) => handler(message.containers));
|
|
937
952
|
break;
|
|
953
|
+
case "sessionList":
|
|
954
|
+
this.sessionListHandlers.forEach(
|
|
955
|
+
(handler) => handler(message.sessions)
|
|
956
|
+
);
|
|
957
|
+
if (this.listSessionsResolve) {
|
|
958
|
+
this.listSessionsResolve(message.sessions);
|
|
959
|
+
this.listSessionsResolve = null;
|
|
960
|
+
}
|
|
961
|
+
break;
|
|
962
|
+
case "joined":
|
|
963
|
+
const joinedSession = message.session;
|
|
964
|
+
const history = message.history;
|
|
965
|
+
this.sessionId = message.sessionId;
|
|
966
|
+
this.sessionInfo = {
|
|
967
|
+
sessionId: joinedSession.sessionId,
|
|
968
|
+
shell: joinedSession.shell,
|
|
969
|
+
cwd: joinedSession.cwd,
|
|
970
|
+
cols: joinedSession.cols,
|
|
971
|
+
rows: joinedSession.rows,
|
|
972
|
+
createdAt: joinedSession.createdAt,
|
|
973
|
+
container: joinedSession.container
|
|
974
|
+
};
|
|
975
|
+
this.joinedHandlers.forEach((handler) => handler(joinedSession, history));
|
|
976
|
+
if (this.joinResolve) {
|
|
977
|
+
this.joinResolve(joinedSession);
|
|
978
|
+
this.joinResolve = null;
|
|
979
|
+
this.joinReject = null;
|
|
980
|
+
}
|
|
981
|
+
break;
|
|
982
|
+
case "left":
|
|
983
|
+
const leftSessionId = message.sessionId;
|
|
984
|
+
if (this.sessionId === leftSessionId) {
|
|
985
|
+
this.sessionId = null;
|
|
986
|
+
this.sessionInfo = null;
|
|
987
|
+
}
|
|
988
|
+
this.leftHandlers.forEach((handler) => handler(leftSessionId));
|
|
989
|
+
break;
|
|
990
|
+
case "clientJoined":
|
|
991
|
+
this.clientJoinedHandlers.forEach(
|
|
992
|
+
(handler) => handler(message.sessionId, message.clientCount)
|
|
993
|
+
);
|
|
994
|
+
break;
|
|
995
|
+
case "clientLeft":
|
|
996
|
+
this.clientLeftHandlers.forEach(
|
|
997
|
+
(handler) => handler(message.sessionId, message.clientCount)
|
|
998
|
+
);
|
|
999
|
+
break;
|
|
1000
|
+
case "sessionClosed":
|
|
1001
|
+
const closedSessionId = message.sessionId;
|
|
1002
|
+
const reason = message.reason;
|
|
1003
|
+
if (this.sessionId === closedSessionId) {
|
|
1004
|
+
this.sessionId = null;
|
|
1005
|
+
this.sessionInfo = null;
|
|
1006
|
+
}
|
|
1007
|
+
this.sessionClosedHandlers.forEach(
|
|
1008
|
+
(handler) => handler(closedSessionId, reason)
|
|
1009
|
+
);
|
|
1010
|
+
break;
|
|
938
1011
|
}
|
|
939
1012
|
}
|
|
940
1013
|
/**
|
|
@@ -947,7 +1020,7 @@ var TerminalClient = class {
|
|
|
947
1020
|
return;
|
|
948
1021
|
}
|
|
949
1022
|
if (this.sessionId) {
|
|
950
|
-
reject(new Error("Session already
|
|
1023
|
+
reject(new Error("Session already active. Call kill() or leave() first."));
|
|
951
1024
|
return;
|
|
952
1025
|
}
|
|
953
1026
|
this.spawnResolve = resolve;
|
|
@@ -1002,7 +1075,7 @@ var TerminalClient = class {
|
|
|
1002
1075
|
);
|
|
1003
1076
|
}
|
|
1004
1077
|
/**
|
|
1005
|
-
* Kill the terminal session
|
|
1078
|
+
* Kill the terminal session (close and terminate)
|
|
1006
1079
|
*/
|
|
1007
1080
|
kill() {
|
|
1008
1081
|
if (!this.ws || this.state !== "connected") {
|
|
@@ -1021,6 +1094,91 @@ var TerminalClient = class {
|
|
|
1021
1094
|
this.sessionInfo = null;
|
|
1022
1095
|
}
|
|
1023
1096
|
// ==========================================
|
|
1097
|
+
// Session Multiplexing Methods
|
|
1098
|
+
// ==========================================
|
|
1099
|
+
/**
|
|
1100
|
+
* List available sessions
|
|
1101
|
+
*/
|
|
1102
|
+
listSessions(filter) {
|
|
1103
|
+
return new Promise((resolve, reject) => {
|
|
1104
|
+
if (this.state !== "connected" || !this.ws) {
|
|
1105
|
+
reject(new Error("Not connected to server"));
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
this.listSessionsResolve = resolve;
|
|
1109
|
+
this.ws.send(
|
|
1110
|
+
JSON.stringify({
|
|
1111
|
+
type: "listSessions",
|
|
1112
|
+
filter
|
|
1113
|
+
})
|
|
1114
|
+
);
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Join an existing session
|
|
1119
|
+
*/
|
|
1120
|
+
join(options) {
|
|
1121
|
+
return new Promise((resolve, reject) => {
|
|
1122
|
+
if (this.state !== "connected" || !this.ws) {
|
|
1123
|
+
reject(new Error("Not connected to server"));
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
if (this.sessionId) {
|
|
1127
|
+
reject(new Error("Already in a session. Call leave() first."));
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
this.joinResolve = resolve;
|
|
1131
|
+
this.joinReject = reject;
|
|
1132
|
+
this.ws.send(
|
|
1133
|
+
JSON.stringify({
|
|
1134
|
+
type: "join",
|
|
1135
|
+
options
|
|
1136
|
+
})
|
|
1137
|
+
);
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Leave the current session without killing it
|
|
1142
|
+
*/
|
|
1143
|
+
leave(sessionId) {
|
|
1144
|
+
if (!this.ws || this.state !== "connected") {
|
|
1145
|
+
console.error("[x-shell] Cannot leave: not connected");
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
const targetSession = sessionId || this.sessionId;
|
|
1149
|
+
if (!targetSession) {
|
|
1150
|
+
console.error("[x-shell] Cannot leave: no active session");
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
this.ws.send(
|
|
1154
|
+
JSON.stringify({
|
|
1155
|
+
type: "leave",
|
|
1156
|
+
sessionId: targetSession
|
|
1157
|
+
})
|
|
1158
|
+
);
|
|
1159
|
+
if (targetSession === this.sessionId) {
|
|
1160
|
+
this.sessionId = null;
|
|
1161
|
+
this.sessionInfo = null;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Request session list and trigger onSessionList handlers
|
|
1166
|
+
* (Fire-and-forget version of listSessions)
|
|
1167
|
+
*/
|
|
1168
|
+
requestSessionList(filter) {
|
|
1169
|
+
this.listSessions(filter).then((sessions) => {
|
|
1170
|
+
this.sessionListHandlers.forEach((handler) => {
|
|
1171
|
+
try {
|
|
1172
|
+
handler(sessions);
|
|
1173
|
+
} catch (e5) {
|
|
1174
|
+
console.error("[x-shell] Error in sessionList handler:", e5);
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1177
|
+
}).catch((err) => {
|
|
1178
|
+
console.error("[x-shell] Failed to list sessions:", err);
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
// ==========================================
|
|
1024
1182
|
// Event handlers
|
|
1025
1183
|
// ==========================================
|
|
1026
1184
|
/**
|
|
@@ -1074,6 +1232,42 @@ var TerminalClient = class {
|
|
|
1074
1232
|
onContainerList(handler) {
|
|
1075
1233
|
this.containerListHandlers.push(handler);
|
|
1076
1234
|
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Called when session list is received
|
|
1237
|
+
*/
|
|
1238
|
+
onSessionList(handler) {
|
|
1239
|
+
this.sessionListHandlers.push(handler);
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Called when successfully joined a session
|
|
1243
|
+
*/
|
|
1244
|
+
onJoined(handler) {
|
|
1245
|
+
this.joinedHandlers.push(handler);
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Called when left a session
|
|
1249
|
+
*/
|
|
1250
|
+
onLeft(handler) {
|
|
1251
|
+
this.leftHandlers.push(handler);
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Called when another client joins the current session
|
|
1255
|
+
*/
|
|
1256
|
+
onClientJoined(handler) {
|
|
1257
|
+
this.clientJoinedHandlers.push(handler);
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Called when another client leaves the current session
|
|
1261
|
+
*/
|
|
1262
|
+
onClientLeft(handler) {
|
|
1263
|
+
this.clientLeftHandlers.push(handler);
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Called when the session is closed by owner or orphan timeout
|
|
1267
|
+
*/
|
|
1268
|
+
onSessionClosed(handler) {
|
|
1269
|
+
this.sessionClosedHandlers.push(handler);
|
|
1270
|
+
}
|
|
1077
1271
|
/**
|
|
1078
1272
|
* Request list of available containers
|
|
1079
1273
|
*/
|
|
@@ -1145,6 +1339,7 @@ var XShellTerminal = class extends i4 {
|
|
|
1145
1339
|
this.showConnectionPanel = false;
|
|
1146
1340
|
this.showSettings = false;
|
|
1147
1341
|
this.showStatusBar = false;
|
|
1342
|
+
this.showTabs = false;
|
|
1148
1343
|
this.fontSize = 14;
|
|
1149
1344
|
this.fontFamily = 'Menlo, Monaco, "Courier New", monospace';
|
|
1150
1345
|
this.client = null;
|
|
@@ -1159,10 +1354,16 @@ var XShellTerminal = class extends i4 {
|
|
|
1159
1354
|
this.serverInfo = null;
|
|
1160
1355
|
this.selectedContainer = "";
|
|
1161
1356
|
this.selectedShell = "/bin/sh";
|
|
1162
|
-
this.connectionMode = "
|
|
1357
|
+
this.connectionMode = "local";
|
|
1358
|
+
this.availableSessions = [];
|
|
1359
|
+
this.selectedSessionId = "";
|
|
1360
|
+
this.clientCount = 1;
|
|
1163
1361
|
this.settingsMenuOpen = false;
|
|
1164
1362
|
this.statusMessage = "";
|
|
1165
1363
|
this.statusType = "info";
|
|
1364
|
+
this.tabs = [];
|
|
1365
|
+
this.activeTabId = "";
|
|
1366
|
+
this.tabCounter = 0;
|
|
1166
1367
|
// xterm.js module (loaded dynamically)
|
|
1167
1368
|
this.xtermModule = null;
|
|
1168
1369
|
this.fitAddonModule = null;
|
|
@@ -1170,6 +1371,9 @@ var XShellTerminal = class extends i4 {
|
|
|
1170
1371
|
}
|
|
1171
1372
|
connectedCallback() {
|
|
1172
1373
|
super.connectedCallback();
|
|
1374
|
+
if (this.showTabs && this.tabs.length === 0) {
|
|
1375
|
+
this.createTab();
|
|
1376
|
+
}
|
|
1173
1377
|
if (this.autoConnect && this.url) {
|
|
1174
1378
|
this.connect();
|
|
1175
1379
|
}
|
|
@@ -1248,23 +1452,55 @@ var XShellTerminal = class extends i4 {
|
|
|
1248
1452
|
new CustomEvent("error", { detail: { error: err }, bubbles: true, composed: true })
|
|
1249
1453
|
);
|
|
1250
1454
|
});
|
|
1455
|
+
const client = this.client;
|
|
1251
1456
|
this.client.onData((data) => {
|
|
1252
|
-
if (this.
|
|
1457
|
+
if (this.showTabs) {
|
|
1458
|
+
const tab = this.tabs.find((t4) => t4.client === client);
|
|
1459
|
+
if (tab?.terminal) {
|
|
1460
|
+
tab.terminal.write(data);
|
|
1461
|
+
}
|
|
1462
|
+
} else if (this.terminal) {
|
|
1253
1463
|
this.terminal.write(data);
|
|
1254
1464
|
}
|
|
1255
1465
|
});
|
|
1256
1466
|
this.client.onExit((code) => {
|
|
1257
|
-
this.
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1467
|
+
if (this.showTabs) {
|
|
1468
|
+
const tab = this.tabs.find((t4) => t4.client === client);
|
|
1469
|
+
if (tab) {
|
|
1470
|
+
tab.sessionActive = false;
|
|
1471
|
+
tab.sessionInfo = null;
|
|
1472
|
+
if (tab.terminal) {
|
|
1473
|
+
tab.terminal.writeln("");
|
|
1474
|
+
tab.terminal.writeln(`\x1B[1;33m[Process exited with code: ${code}]\x1B[0m`);
|
|
1475
|
+
}
|
|
1476
|
+
if (tab.id === this.activeTabId) {
|
|
1477
|
+
this.sessionActive = false;
|
|
1478
|
+
this.sessionInfo = null;
|
|
1479
|
+
}
|
|
1480
|
+
this.tabs = [...this.tabs];
|
|
1481
|
+
}
|
|
1482
|
+
} else {
|
|
1483
|
+
this.sessionActive = false;
|
|
1484
|
+
this.sessionInfo = null;
|
|
1485
|
+
if (this.terminal) {
|
|
1486
|
+
this.terminal.writeln("");
|
|
1487
|
+
this.terminal.writeln(`\x1B[1;33m[Process exited with code: ${code}]\x1B[0m`);
|
|
1488
|
+
}
|
|
1262
1489
|
}
|
|
1263
1490
|
this.dispatchEvent(
|
|
1264
1491
|
new CustomEvent("exit", { detail: { exitCode: code }, bubbles: true, composed: true })
|
|
1265
1492
|
);
|
|
1266
1493
|
});
|
|
1267
1494
|
this.client.onSpawned((info) => {
|
|
1495
|
+
if (this.showTabs) {
|
|
1496
|
+
const tab = this.tabs.find((t4) => t4.client === client);
|
|
1497
|
+
if (tab) {
|
|
1498
|
+
tab.sessionInfo = info;
|
|
1499
|
+
tab.sessionActive = true;
|
|
1500
|
+
tab.label = info.container || info.shell.split("/").pop() || "Terminal";
|
|
1501
|
+
this.tabs = [...this.tabs];
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1268
1504
|
this.sessionInfo = info;
|
|
1269
1505
|
this.setStatus(`Session started: ${info.container || info.shell}`, "success");
|
|
1270
1506
|
this.dispatchEvent(
|
|
@@ -1285,7 +1521,55 @@ var XShellTerminal = class extends i4 {
|
|
|
1285
1521
|
this.selectedContainer = containers[0].name;
|
|
1286
1522
|
}
|
|
1287
1523
|
});
|
|
1524
|
+
this.client.onSessionList((sessions) => {
|
|
1525
|
+
this.availableSessions = sessions;
|
|
1526
|
+
if (sessions.length > 0 && !this.selectedSessionId) {
|
|
1527
|
+
this.selectedSessionId = sessions[0].sessionId;
|
|
1528
|
+
}
|
|
1529
|
+
});
|
|
1530
|
+
this.client.onClientJoined((sessionId, count) => {
|
|
1531
|
+
if (this.showTabs) {
|
|
1532
|
+
const tab = this.tabs.find((t4) => t4.client === client);
|
|
1533
|
+
if (tab) {
|
|
1534
|
+
tab.clientCount = count;
|
|
1535
|
+
this.tabs = [...this.tabs];
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
this.clientCount = count;
|
|
1539
|
+
this.setStatus(`Client joined (${count} total)`, "info");
|
|
1540
|
+
});
|
|
1541
|
+
this.client.onClientLeft((sessionId, count) => {
|
|
1542
|
+
if (this.showTabs) {
|
|
1543
|
+
const tab = this.tabs.find((t4) => t4.client === client);
|
|
1544
|
+
if (tab) {
|
|
1545
|
+
tab.clientCount = count;
|
|
1546
|
+
this.tabs = [...this.tabs];
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
this.clientCount = count;
|
|
1550
|
+
this.setStatus(`Client left (${count} remaining)`, "info");
|
|
1551
|
+
});
|
|
1552
|
+
this.client.onSessionClosed((sessionId, reason) => {
|
|
1553
|
+
if (this.showTabs) {
|
|
1554
|
+
const tab = this.tabs.find((t4) => t4.client === client && t4.sessionInfo?.sessionId === sessionId);
|
|
1555
|
+
if (tab) {
|
|
1556
|
+
tab.sessionActive = false;
|
|
1557
|
+
tab.sessionInfo = null;
|
|
1558
|
+
this.tabs = [...this.tabs];
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
if (this.sessionInfo?.sessionId === sessionId) {
|
|
1562
|
+
this.sessionActive = false;
|
|
1563
|
+
this.sessionInfo = null;
|
|
1564
|
+
this.setStatus(`Session closed: ${reason}`, "info");
|
|
1565
|
+
}
|
|
1566
|
+
client.requestSessionList();
|
|
1567
|
+
});
|
|
1288
1568
|
await this.client.connect();
|
|
1569
|
+
this.client.requestSessionList();
|
|
1570
|
+
if (this.showTabs) {
|
|
1571
|
+
this.syncStateToActiveTab();
|
|
1572
|
+
}
|
|
1289
1573
|
} catch (err) {
|
|
1290
1574
|
this.error = err instanceof Error ? err.message : "Connection failed";
|
|
1291
1575
|
} finally {
|
|
@@ -1329,6 +1613,10 @@ var XShellTerminal = class extends i4 {
|
|
|
1329
1613
|
const info = await this.client.spawn(spawnOptions);
|
|
1330
1614
|
this.sessionActive = true;
|
|
1331
1615
|
this.sessionInfo = info;
|
|
1616
|
+
if (this.showTabs) {
|
|
1617
|
+
this.syncStateToActiveTab();
|
|
1618
|
+
}
|
|
1619
|
+
this.client.requestSessionList();
|
|
1332
1620
|
if (this.terminal) {
|
|
1333
1621
|
this.terminal.focus();
|
|
1334
1622
|
}
|
|
@@ -1348,7 +1636,12 @@ var XShellTerminal = class extends i4 {
|
|
|
1348
1636
|
return;
|
|
1349
1637
|
await this.loadXterm();
|
|
1350
1638
|
await this.updateComplete;
|
|
1351
|
-
|
|
1639
|
+
let container = null;
|
|
1640
|
+
if (this.showTabs && this.activeTabId) {
|
|
1641
|
+
container = this.shadowRoot?.querySelector(`.tab-terminal-container[data-tab-id="${this.activeTabId}"]`) ?? null;
|
|
1642
|
+
} else {
|
|
1643
|
+
container = this.shadowRoot?.querySelector(".terminal-container") ?? null;
|
|
1644
|
+
}
|
|
1352
1645
|
if (!container)
|
|
1353
1646
|
return;
|
|
1354
1647
|
const terminalTheme = this.getTerminalTheme();
|
|
@@ -1378,12 +1671,22 @@ var XShellTerminal = class extends i4 {
|
|
|
1378
1671
|
this.client.resize(cols, rows);
|
|
1379
1672
|
}
|
|
1380
1673
|
});
|
|
1381
|
-
this.resizeObserver
|
|
1382
|
-
|
|
1383
|
-
this.
|
|
1384
|
-
|
|
1385
|
-
|
|
1674
|
+
if (!this.resizeObserver) {
|
|
1675
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
1676
|
+
if (this.showTabs) {
|
|
1677
|
+
const activeTab = this.getActiveTab();
|
|
1678
|
+
if (activeTab?.fitAddon) {
|
|
1679
|
+
activeTab.fitAddon.fit();
|
|
1680
|
+
}
|
|
1681
|
+
} else if (this.fitAddon) {
|
|
1682
|
+
this.fitAddon.fit();
|
|
1683
|
+
}
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1386
1686
|
this.resizeObserver.observe(container);
|
|
1687
|
+
if (this.showTabs) {
|
|
1688
|
+
this.syncStateToActiveTab();
|
|
1689
|
+
}
|
|
1387
1690
|
}
|
|
1388
1691
|
/**
|
|
1389
1692
|
* Get terminal theme based on component theme
|
|
@@ -1472,6 +1775,141 @@ var XShellTerminal = class extends i4 {
|
|
|
1472
1775
|
}
|
|
1473
1776
|
this.fitAddon = null;
|
|
1474
1777
|
}
|
|
1778
|
+
// ==================== Tab Management Methods ====================
|
|
1779
|
+
/**
|
|
1780
|
+
* Get the active tab
|
|
1781
|
+
*/
|
|
1782
|
+
getActiveTab() {
|
|
1783
|
+
return this.tabs.find((t4) => t4.id === this.activeTabId);
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Create a new tab
|
|
1787
|
+
*/
|
|
1788
|
+
createTab(label) {
|
|
1789
|
+
this.tabCounter++;
|
|
1790
|
+
const tab = {
|
|
1791
|
+
id: `tab-${this.tabCounter}`,
|
|
1792
|
+
label: label || `Terminal ${this.tabCounter}`,
|
|
1793
|
+
client: null,
|
|
1794
|
+
terminal: null,
|
|
1795
|
+
fitAddon: null,
|
|
1796
|
+
connected: false,
|
|
1797
|
+
sessionActive: false,
|
|
1798
|
+
sessionInfo: null,
|
|
1799
|
+
clientCount: 1,
|
|
1800
|
+
containerEl: null
|
|
1801
|
+
};
|
|
1802
|
+
this.tabs = [...this.tabs, tab];
|
|
1803
|
+
this.switchTab(tab.id);
|
|
1804
|
+
return tab;
|
|
1805
|
+
}
|
|
1806
|
+
/**
|
|
1807
|
+
* Switch to a tab
|
|
1808
|
+
*/
|
|
1809
|
+
switchTab(tabId) {
|
|
1810
|
+
const tab = this.tabs.find((t4) => t4.id === tabId);
|
|
1811
|
+
if (!tab)
|
|
1812
|
+
return;
|
|
1813
|
+
this.activeTabId = tabId;
|
|
1814
|
+
this.client = tab.client;
|
|
1815
|
+
this.terminal = tab.terminal;
|
|
1816
|
+
this.fitAddon = tab.fitAddon;
|
|
1817
|
+
this.connected = tab.connected;
|
|
1818
|
+
this.sessionActive = tab.sessionActive;
|
|
1819
|
+
this.sessionInfo = tab.sessionInfo;
|
|
1820
|
+
this.clientCount = tab.clientCount;
|
|
1821
|
+
this.updateComplete.then(() => {
|
|
1822
|
+
if (tab.terminal) {
|
|
1823
|
+
tab.terminal.focus();
|
|
1824
|
+
}
|
|
1825
|
+
if (tab.fitAddon) {
|
|
1826
|
+
tab.fitAddon.fit();
|
|
1827
|
+
}
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Close a tab
|
|
1832
|
+
*/
|
|
1833
|
+
closeTab(tabId) {
|
|
1834
|
+
const tabIndex = this.tabs.findIndex((t4) => t4.id === tabId);
|
|
1835
|
+
if (tabIndex === -1)
|
|
1836
|
+
return;
|
|
1837
|
+
const tab = this.tabs[tabIndex];
|
|
1838
|
+
if (tab.terminal) {
|
|
1839
|
+
tab.terminal.dispose();
|
|
1840
|
+
}
|
|
1841
|
+
if (tab.client) {
|
|
1842
|
+
tab.client.disconnect();
|
|
1843
|
+
}
|
|
1844
|
+
this.tabs = this.tabs.filter((t4) => t4.id !== tabId);
|
|
1845
|
+
if (this.activeTabId === tabId && this.tabs.length > 0) {
|
|
1846
|
+
const newIndex = Math.max(0, tabIndex - 1);
|
|
1847
|
+
this.switchTab(this.tabs[newIndex].id);
|
|
1848
|
+
}
|
|
1849
|
+
if (this.tabs.length === 0) {
|
|
1850
|
+
this.activeTabId = "";
|
|
1851
|
+
this.client = null;
|
|
1852
|
+
this.terminal = null;
|
|
1853
|
+
this.fitAddon = null;
|
|
1854
|
+
this.connected = false;
|
|
1855
|
+
this.sessionActive = false;
|
|
1856
|
+
this.sessionInfo = null;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Update the active tab's state from component state
|
|
1861
|
+
*/
|
|
1862
|
+
syncStateToActiveTab() {
|
|
1863
|
+
const tab = this.getActiveTab();
|
|
1864
|
+
if (tab) {
|
|
1865
|
+
tab.client = this.client;
|
|
1866
|
+
tab.terminal = this.terminal;
|
|
1867
|
+
tab.fitAddon = this.fitAddon;
|
|
1868
|
+
tab.connected = this.connected;
|
|
1869
|
+
tab.sessionActive = this.sessionActive;
|
|
1870
|
+
tab.sessionInfo = this.sessionInfo;
|
|
1871
|
+
tab.clientCount = this.clientCount;
|
|
1872
|
+
this.tabs = [...this.tabs];
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
/**
|
|
1876
|
+
* Render the tab bar
|
|
1877
|
+
*/
|
|
1878
|
+
renderTabBar() {
|
|
1879
|
+
if (!this.showTabs)
|
|
1880
|
+
return A;
|
|
1881
|
+
return b2`
|
|
1882
|
+
<div class="tab-bar">
|
|
1883
|
+
${this.tabs.map(
|
|
1884
|
+
(tab) => b2`
|
|
1885
|
+
<button
|
|
1886
|
+
class="tab ${tab.id === this.activeTabId ? "active" : ""}"
|
|
1887
|
+
@click=${() => this.switchTab(tab.id)}
|
|
1888
|
+
>
|
|
1889
|
+
<span class="tab-status ${tab.sessionActive ? "connected" : ""}"></span>
|
|
1890
|
+
<span>${tab.label}</span>
|
|
1891
|
+
${this.tabs.length > 1 ? b2`
|
|
1892
|
+
<button
|
|
1893
|
+
class="tab-close"
|
|
1894
|
+
@click=${(e5) => {
|
|
1895
|
+
e5.stopPropagation();
|
|
1896
|
+
this.closeTab(tab.id);
|
|
1897
|
+
}}
|
|
1898
|
+
title="Close tab"
|
|
1899
|
+
>
|
|
1900
|
+
×
|
|
1901
|
+
</button>
|
|
1902
|
+
` : A}
|
|
1903
|
+
</button>
|
|
1904
|
+
`
|
|
1905
|
+
)}
|
|
1906
|
+
<button class="tab-add" @click=${() => this.createTab()} title="New tab">
|
|
1907
|
+
+
|
|
1908
|
+
</button>
|
|
1909
|
+
</div>
|
|
1910
|
+
`;
|
|
1911
|
+
}
|
|
1912
|
+
// ==================== End Tab Management Methods ====================
|
|
1475
1913
|
/**
|
|
1476
1914
|
* Set status message
|
|
1477
1915
|
*/
|
|
@@ -1535,6 +1973,70 @@ var XShellTerminal = class extends i4 {
|
|
|
1535
1973
|
if (this.connectionMode === "docker" && this.client && this.connected) {
|
|
1536
1974
|
this.client.requestContainerList();
|
|
1537
1975
|
}
|
|
1976
|
+
if (this.connectionMode === "join" && this.client && this.connected) {
|
|
1977
|
+
this.client.requestSessionList();
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
/**
|
|
1981
|
+
* Refresh session list
|
|
1982
|
+
*/
|
|
1983
|
+
refreshSessions() {
|
|
1984
|
+
if (this.client && this.connected) {
|
|
1985
|
+
this.client.requestSessionList();
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Join an existing session
|
|
1990
|
+
*/
|
|
1991
|
+
async join(sessionId, requestHistory = true) {
|
|
1992
|
+
if (!this.client || !this.connected) {
|
|
1993
|
+
throw new Error("Not connected to server");
|
|
1994
|
+
}
|
|
1995
|
+
this.loading = true;
|
|
1996
|
+
this.error = null;
|
|
1997
|
+
try {
|
|
1998
|
+
await this.initTerminalUI();
|
|
1999
|
+
const session = await this.client.join({
|
|
2000
|
+
sessionId,
|
|
2001
|
+
requestHistory,
|
|
2002
|
+
historyLimit: 5e4
|
|
2003
|
+
});
|
|
2004
|
+
this.sessionActive = true;
|
|
2005
|
+
this.sessionInfo = {
|
|
2006
|
+
sessionId: session.sessionId,
|
|
2007
|
+
shell: session.shell,
|
|
2008
|
+
cwd: session.cwd,
|
|
2009
|
+
cols: session.cols,
|
|
2010
|
+
rows: session.rows,
|
|
2011
|
+
createdAt: session.createdAt || /* @__PURE__ */ new Date(),
|
|
2012
|
+
container: session.container
|
|
2013
|
+
};
|
|
2014
|
+
this.clientCount = session.clientCount;
|
|
2015
|
+
if (this.showTabs) {
|
|
2016
|
+
this.syncStateToActiveTab();
|
|
2017
|
+
}
|
|
2018
|
+
this.setStatus(`Joined session (${session.clientCount} clients)`, "success");
|
|
2019
|
+
if (this.terminal) {
|
|
2020
|
+
this.terminal.focus();
|
|
2021
|
+
}
|
|
2022
|
+
return session;
|
|
2023
|
+
} catch (err) {
|
|
2024
|
+
this.error = err instanceof Error ? err.message : "Failed to join session";
|
|
2025
|
+
throw err;
|
|
2026
|
+
} finally {
|
|
2027
|
+
this.loading = false;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Leave current session without killing it
|
|
2032
|
+
*/
|
|
2033
|
+
leave() {
|
|
2034
|
+
if (this.client && this.sessionInfo) {
|
|
2035
|
+
this.client.leave(this.sessionInfo.sessionId);
|
|
2036
|
+
this.sessionActive = false;
|
|
2037
|
+
this.sessionInfo = null;
|
|
2038
|
+
this.setStatus("Left session", "info");
|
|
2039
|
+
}
|
|
1538
2040
|
}
|
|
1539
2041
|
/**
|
|
1540
2042
|
* Handle connect from connection panel
|
|
@@ -1544,14 +2046,18 @@ var XShellTerminal = class extends i4 {
|
|
|
1544
2046
|
await this.connect();
|
|
1545
2047
|
}
|
|
1546
2048
|
if (this.connected) {
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
options.container = this.selectedContainer;
|
|
1550
|
-
options.containerShell = this.selectedShell || "/bin/sh";
|
|
2049
|
+
if (this.connectionMode === "join" && this.selectedSessionId) {
|
|
2050
|
+
await this.join(this.selectedSessionId);
|
|
1551
2051
|
} else {
|
|
1552
|
-
options
|
|
2052
|
+
const options = {};
|
|
2053
|
+
if (this.connectionMode === "docker" && this.selectedContainer) {
|
|
2054
|
+
options.container = this.selectedContainer;
|
|
2055
|
+
options.containerShell = this.selectedShell || "/bin/sh";
|
|
2056
|
+
} else {
|
|
2057
|
+
options.shell = this.selectedShell || void 0;
|
|
2058
|
+
}
|
|
2059
|
+
await this.spawn(options);
|
|
1553
2060
|
}
|
|
1554
|
-
await this.spawn(options);
|
|
1555
2061
|
}
|
|
1556
2062
|
}
|
|
1557
2063
|
/**
|
|
@@ -1567,11 +2073,13 @@ var XShellTerminal = class extends i4 {
|
|
|
1567
2073
|
if (!this.showConnectionPanel)
|
|
1568
2074
|
return A;
|
|
1569
2075
|
const runningContainers = this.containers.filter((c4) => c4.state === "running");
|
|
2076
|
+
const acceptingSessions = this.availableSessions.filter((s4) => s4.accepting);
|
|
1570
2077
|
return b2`
|
|
1571
2078
|
<div class="connection-panel">
|
|
1572
2079
|
<div class="connection-panel-title">
|
|
1573
2080
|
<span>Connection</span>
|
|
1574
|
-
${this.
|
|
2081
|
+
${this.availableSessions.length > 0 ? b2`<span style="font-size: 11px; color: var(--xs-status-connected);">${this.availableSessions.length} session(s) available</span>` : A}
|
|
2082
|
+
${this.serverInfo?.dockerEnabled ? b2`<span style="font-size: 11px; color: var(--xs-text-muted);">Docker enabled</span>` : A}
|
|
1575
2083
|
</div>
|
|
1576
2084
|
<div class="connection-form">
|
|
1577
2085
|
<div class="form-group">
|
|
@@ -1581,12 +2089,37 @@ var XShellTerminal = class extends i4 {
|
|
|
1581
2089
|
@change=${this.handleModeChange}
|
|
1582
2090
|
?disabled=${this.sessionActive}
|
|
1583
2091
|
>
|
|
1584
|
-
<option value="local">Local Shell</option>
|
|
1585
|
-
${this.serverInfo?.dockerEnabled ? b2`<option value="docker">Docker
|
|
2092
|
+
<option value="local">New Local Shell</option>
|
|
2093
|
+
${this.serverInfo?.dockerEnabled ? b2`<option value="docker">New Docker Session</option>` : A}
|
|
2094
|
+
${acceptingSessions.length > 0 ? b2`<option value="join">Join Existing Session</option>` : A}
|
|
1586
2095
|
</select>
|
|
1587
2096
|
</div>
|
|
1588
2097
|
|
|
1589
|
-
${this.connectionMode === "
|
|
2098
|
+
${this.connectionMode === "join" ? b2`
|
|
2099
|
+
<div class="form-group">
|
|
2100
|
+
<label style="display: flex; justify-content: space-between; align-items: center;">
|
|
2101
|
+
<span>Session</span>
|
|
2102
|
+
<button
|
|
2103
|
+
style="font-size: 10px; padding: 2px 6px;"
|
|
2104
|
+
@click=${this.refreshSessions}
|
|
2105
|
+
?disabled=${!this.connected}
|
|
2106
|
+
>Refresh</button>
|
|
2107
|
+
</label>
|
|
2108
|
+
<select
|
|
2109
|
+
.value=${this.selectedSessionId}
|
|
2110
|
+
@change=${(e5) => this.selectedSessionId = e5.target.value}
|
|
2111
|
+
?disabled=${this.sessionActive}
|
|
2112
|
+
>
|
|
2113
|
+
${acceptingSessions.length === 0 ? b2`<option value="">No sessions available</option>` : acceptingSessions.map((s4) => b2`
|
|
2114
|
+
<option value=${s4.sessionId}>
|
|
2115
|
+
${s4.label || s4.sessionId.substring(0, 12)}
|
|
2116
|
+
(${s4.type === "local" ? s4.shell : s4.container || s4.type})
|
|
2117
|
+
- ${s4.clientCount} client(s)
|
|
2118
|
+
</option>
|
|
2119
|
+
`)}
|
|
2120
|
+
</select>
|
|
2121
|
+
</div>
|
|
2122
|
+
` : this.connectionMode === "docker" ? b2`
|
|
1590
2123
|
<div class="form-group">
|
|
1591
2124
|
<label>Container</label>
|
|
1592
2125
|
<select
|
|
@@ -1601,28 +2134,30 @@ var XShellTerminal = class extends i4 {
|
|
|
1601
2134
|
</div>
|
|
1602
2135
|
` : A}
|
|
1603
2136
|
|
|
1604
|
-
|
|
1605
|
-
<
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
2137
|
+
${this.connectionMode !== "join" ? b2`
|
|
2138
|
+
<div class="form-group">
|
|
2139
|
+
<label>Shell</label>
|
|
2140
|
+
<select
|
|
2141
|
+
.value=${this.selectedShell}
|
|
2142
|
+
@change=${(e5) => this.selectedShell = e5.target.value}
|
|
2143
|
+
?disabled=${this.sessionActive}
|
|
2144
|
+
>
|
|
2145
|
+
${this.serverInfo?.allowedShells.length ? this.serverInfo.allowedShells.map((s4) => b2`<option value=${s4}>${s4}</option>`) : b2`
|
|
2146
|
+
<option value="/bin/bash">/bin/bash</option>
|
|
2147
|
+
<option value="/bin/sh">/bin/sh</option>
|
|
2148
|
+
<option value="/bin/zsh">/bin/zsh</option>
|
|
2149
|
+
`}
|
|
2150
|
+
</select>
|
|
2151
|
+
</div>
|
|
2152
|
+
` : A}
|
|
1618
2153
|
|
|
1619
2154
|
<div class="form-group">
|
|
1620
2155
|
${!this.connected ? b2`<button class="btn-primary" @click=${this.handlePanelConnect} ?disabled=${this.loading}>
|
|
1621
2156
|
${this.loading ? "Connecting..." : "Connect"}
|
|
1622
|
-
</button>` : !this.sessionActive ? b2`<button class="btn-primary" @click=${this.handlePanelConnect} ?disabled=${this.loading}>
|
|
1623
|
-
${this.loading ? "Starting..." : "Start Session"}
|
|
2157
|
+
</button>` : !this.sessionActive ? b2`<button class="btn-primary" @click=${this.handlePanelConnect} ?disabled=${this.loading || this.connectionMode === "join" && !this.selectedSessionId}>
|
|
2158
|
+
${this.loading ? "Starting..." : this.connectionMode === "join" ? "Join Session" : "Start Session"}
|
|
1624
2159
|
</button>` : b2`<button class="btn-danger" @click=${this.kill}>
|
|
1625
|
-
Stop Session
|
|
2160
|
+
${this.clientCount > 1 ? "Leave Session" : "Stop Session"}
|
|
1626
2161
|
</button>`}
|
|
1627
2162
|
</div>
|
|
1628
2163
|
</div>
|
|
@@ -1742,10 +2277,26 @@ var XShellTerminal = class extends i4 {
|
|
|
1742
2277
|
`}
|
|
1743
2278
|
|
|
1744
2279
|
${this.renderConnectionPanel()}
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
2280
|
+
${this.renderTabBar()}
|
|
2281
|
+
|
|
2282
|
+
${this.showTabs && this.tabs.length > 0 ? b2`
|
|
2283
|
+
<div class="terminals-wrapper">
|
|
2284
|
+
${this.tabs.map(
|
|
2285
|
+
(tab) => b2`
|
|
2286
|
+
<div
|
|
2287
|
+
class="tab-terminal-container ${tab.id === this.activeTabId ? "active" : ""}"
|
|
2288
|
+
data-tab-id=${tab.id}
|
|
2289
|
+
>
|
|
2290
|
+
${this.loading && tab.id === this.activeTabId && !tab.terminal ? b2`<div class="loading"><span class="loading-spinner">⏳</span> Loading...</div>` : this.error && tab.id === this.activeTabId && !tab.terminal ? b2`<div class="error">❌ ${this.error}</div>` : A}
|
|
2291
|
+
</div>
|
|
2292
|
+
`
|
|
2293
|
+
)}
|
|
2294
|
+
</div>
|
|
2295
|
+
` : b2`
|
|
2296
|
+
<div class="terminal-container">
|
|
2297
|
+
${this.loading && !this.terminal ? b2`<div class="loading"><span class="loading-spinner">⏳</span> Loading...</div>` : this.error && !this.terminal ? b2`<div class="error">❌ ${this.error}</div>` : A}
|
|
2298
|
+
</div>
|
|
2299
|
+
`}
|
|
1749
2300
|
|
|
1750
2301
|
${this.renderStatusBar()}
|
|
1751
2302
|
`;
|
|
@@ -1987,6 +2538,135 @@ XShellTerminal.styles = [
|
|
|
1987
2538
|
.status-bar-success {
|
|
1988
2539
|
color: var(--xs-status-connected);
|
|
1989
2540
|
}
|
|
2541
|
+
|
|
2542
|
+
/* Tab bar styles */
|
|
2543
|
+
.tab-bar {
|
|
2544
|
+
display: flex;
|
|
2545
|
+
align-items: center;
|
|
2546
|
+
background: var(--xs-bg-header);
|
|
2547
|
+
border-bottom: 1px solid var(--xs-border);
|
|
2548
|
+
padding: 0 4px;
|
|
2549
|
+
gap: 2px;
|
|
2550
|
+
min-height: 32px;
|
|
2551
|
+
overflow-x: auto;
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
.tab-bar::-webkit-scrollbar {
|
|
2555
|
+
height: 4px;
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
.tab-bar::-webkit-scrollbar-thumb {
|
|
2559
|
+
background: var(--xs-border);
|
|
2560
|
+
border-radius: 2px;
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
.tab {
|
|
2564
|
+
display: flex;
|
|
2565
|
+
align-items: center;
|
|
2566
|
+
gap: 6px;
|
|
2567
|
+
padding: 6px 12px;
|
|
2568
|
+
background: transparent;
|
|
2569
|
+
border: none;
|
|
2570
|
+
border-bottom: 2px solid transparent;
|
|
2571
|
+
color: var(--xs-text-muted);
|
|
2572
|
+
font-size: 12px;
|
|
2573
|
+
cursor: pointer;
|
|
2574
|
+
white-space: nowrap;
|
|
2575
|
+
transition: all 0.15s ease;
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
.tab:hover {
|
|
2579
|
+
background: var(--xs-btn-hover);
|
|
2580
|
+
color: var(--xs-text);
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
.tab.active {
|
|
2584
|
+
color: var(--xs-text);
|
|
2585
|
+
border-bottom-color: var(--xs-status-connected);
|
|
2586
|
+
background: var(--xs-bg);
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
.tab-status {
|
|
2590
|
+
width: 6px;
|
|
2591
|
+
height: 6px;
|
|
2592
|
+
border-radius: 50%;
|
|
2593
|
+
background: var(--xs-status-disconnected);
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
.tab-status.connected {
|
|
2597
|
+
background: var(--xs-status-connected);
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
.tab-close {
|
|
2601
|
+
display: flex;
|
|
2602
|
+
align-items: center;
|
|
2603
|
+
justify-content: center;
|
|
2604
|
+
width: 16px;
|
|
2605
|
+
height: 16px;
|
|
2606
|
+
border-radius: 3px;
|
|
2607
|
+
background: none;
|
|
2608
|
+
border: none;
|
|
2609
|
+
color: var(--xs-text-muted);
|
|
2610
|
+
font-size: 14px;
|
|
2611
|
+
cursor: pointer;
|
|
2612
|
+
opacity: 0;
|
|
2613
|
+
transition: opacity 0.15s ease;
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
.tab:hover .tab-close {
|
|
2617
|
+
opacity: 1;
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
.tab-close:hover {
|
|
2621
|
+
background: var(--xs-btn-hover);
|
|
2622
|
+
color: var(--xs-text);
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
.tab-add {
|
|
2626
|
+
display: flex;
|
|
2627
|
+
align-items: center;
|
|
2628
|
+
justify-content: center;
|
|
2629
|
+
width: 28px;
|
|
2630
|
+
height: 28px;
|
|
2631
|
+
border-radius: 4px;
|
|
2632
|
+
background: none;
|
|
2633
|
+
border: none;
|
|
2634
|
+
color: var(--xs-text-muted);
|
|
2635
|
+
font-size: 18px;
|
|
2636
|
+
cursor: pointer;
|
|
2637
|
+
margin-left: 4px;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
.tab-add:hover {
|
|
2641
|
+
background: var(--xs-btn-hover);
|
|
2642
|
+
color: var(--xs-text);
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
/* Multi-terminal container */
|
|
2646
|
+
.terminals-wrapper {
|
|
2647
|
+
flex: 1;
|
|
2648
|
+
position: relative;
|
|
2649
|
+
overflow: hidden;
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
.tab-terminal-container {
|
|
2653
|
+
position: absolute;
|
|
2654
|
+
top: 0;
|
|
2655
|
+
left: 0;
|
|
2656
|
+
right: 0;
|
|
2657
|
+
bottom: 0;
|
|
2658
|
+
padding: 4px;
|
|
2659
|
+
background: var(--xs-terminal-bg);
|
|
2660
|
+
display: none;
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
.tab-terminal-container.active {
|
|
2664
|
+
display: block;
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
.tab-terminal-container .xterm {
|
|
2668
|
+
height: 100%;
|
|
2669
|
+
}
|
|
1990
2670
|
`
|
|
1991
2671
|
];
|
|
1992
2672
|
__decorateClass([
|
|
@@ -2037,6 +2717,9 @@ __decorateClass([
|
|
|
2037
2717
|
__decorateClass([
|
|
2038
2718
|
n4({ type: Boolean, attribute: "show-status-bar" })
|
|
2039
2719
|
], XShellTerminal.prototype, "showStatusBar", 2);
|
|
2720
|
+
__decorateClass([
|
|
2721
|
+
n4({ type: Boolean, attribute: "show-tabs" })
|
|
2722
|
+
], XShellTerminal.prototype, "showTabs", 2);
|
|
2040
2723
|
__decorateClass([
|
|
2041
2724
|
n4({ type: Number, attribute: "font-size" })
|
|
2042
2725
|
], XShellTerminal.prototype, "fontSize", 2);
|
|
@@ -2082,6 +2765,15 @@ __decorateClass([
|
|
|
2082
2765
|
__decorateClass([
|
|
2083
2766
|
r5()
|
|
2084
2767
|
], XShellTerminal.prototype, "connectionMode", 2);
|
|
2768
|
+
__decorateClass([
|
|
2769
|
+
r5()
|
|
2770
|
+
], XShellTerminal.prototype, "availableSessions", 2);
|
|
2771
|
+
__decorateClass([
|
|
2772
|
+
r5()
|
|
2773
|
+
], XShellTerminal.prototype, "selectedSessionId", 2);
|
|
2774
|
+
__decorateClass([
|
|
2775
|
+
r5()
|
|
2776
|
+
], XShellTerminal.prototype, "clientCount", 2);
|
|
2085
2777
|
__decorateClass([
|
|
2086
2778
|
r5()
|
|
2087
2779
|
], XShellTerminal.prototype, "settingsMenuOpen", 2);
|
|
@@ -2091,6 +2783,12 @@ __decorateClass([
|
|
|
2091
2783
|
__decorateClass([
|
|
2092
2784
|
r5()
|
|
2093
2785
|
], XShellTerminal.prototype, "statusType", 2);
|
|
2786
|
+
__decorateClass([
|
|
2787
|
+
r5()
|
|
2788
|
+
], XShellTerminal.prototype, "tabs", 2);
|
|
2789
|
+
__decorateClass([
|
|
2790
|
+
r5()
|
|
2791
|
+
], XShellTerminal.prototype, "activeTabId", 2);
|
|
2094
2792
|
XShellTerminal = __decorateClass([
|
|
2095
2793
|
t3("x-shell-terminal")
|
|
2096
2794
|
], XShellTerminal);
|