x-shell.js 0.1.1 → 1.0.0-rc.1
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 +183 -3
- package/dist/client/browser-bundle.js +43 -1
- package/dist/client/browser-bundle.js.map +2 -2
- package/dist/client/terminal-client.d.ts +20 -1
- package/dist/client/terminal-client.d.ts.map +1 -1
- package/dist/client/terminal-client.js +43 -0
- package/dist/client/terminal-client.js.map +1 -1
- package/dist/server/terminal-server.d.ts +25 -1
- package/dist/server/terminal-server.d.ts.map +1 -1
- package/dist/server/terminal-server.js +210 -25
- package/dist/server/terminal-server.js.map +1 -1
- package/dist/shared/types.d.ts +59 -2
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/ui/browser-bundle.js +571 -15
- package/dist/ui/browser-bundle.js.map +3 -3
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/styles.js +22 -0
- package/dist/ui/styles.js.map +1 -1
- package/dist/ui/x-shell-terminal.d.ts +65 -2
- package/dist/ui/x-shell-terminal.d.ts.map +1 -1
- package/dist/ui/x-shell-terminal.js +536 -13
- package/dist/ui/x-shell-terminal.js.map +1 -1
- package/package.json +5 -3
|
@@ -748,6 +748,28 @@ var buttonStyles = i`
|
|
|
748
748
|
opacity: 0.5;
|
|
749
749
|
cursor: not-allowed;
|
|
750
750
|
}
|
|
751
|
+
|
|
752
|
+
button.btn-primary,
|
|
753
|
+
.btn-primary {
|
|
754
|
+
background: var(--xs-status-connected);
|
|
755
|
+
color: #ffffff;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
button.btn-primary:hover,
|
|
759
|
+
.btn-primary:hover {
|
|
760
|
+
background: #16a34a;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
button.btn-danger,
|
|
764
|
+
.btn-danger {
|
|
765
|
+
background: var(--xs-status-disconnected);
|
|
766
|
+
color: #ffffff;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
button.btn-danger:hover,
|
|
770
|
+
.btn-danger:hover {
|
|
771
|
+
background: #dc2626;
|
|
772
|
+
}
|
|
751
773
|
`;
|
|
752
774
|
|
|
753
775
|
// src/client/terminal-client.ts
|
|
@@ -757,6 +779,7 @@ var TerminalClient = class {
|
|
|
757
779
|
this.state = "disconnected";
|
|
758
780
|
this.sessionId = null;
|
|
759
781
|
this.sessionInfo = null;
|
|
782
|
+
this.serverInfo = null;
|
|
760
783
|
this.reconnectAttempts = 0;
|
|
761
784
|
this.reconnectTimeout = null;
|
|
762
785
|
// Event handlers
|
|
@@ -766,6 +789,8 @@ var TerminalClient = class {
|
|
|
766
789
|
this.exitHandlers = [];
|
|
767
790
|
this.errorHandlers = [];
|
|
768
791
|
this.spawnedHandlers = [];
|
|
792
|
+
this.serverInfoHandlers = [];
|
|
793
|
+
this.containerListHandlers = [];
|
|
769
794
|
// Promise resolvers for spawn
|
|
770
795
|
this.spawnResolve = null;
|
|
771
796
|
this.spawnReject = null;
|
|
@@ -875,7 +900,8 @@ var TerminalClient = class {
|
|
|
875
900
|
cwd: message.cwd,
|
|
876
901
|
cols: message.cols,
|
|
877
902
|
rows: message.rows,
|
|
878
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
903
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
904
|
+
container: message.container
|
|
879
905
|
};
|
|
880
906
|
this.spawnedHandlers.forEach((handler) => handler(this.sessionInfo));
|
|
881
907
|
if (this.spawnResolve) {
|
|
@@ -902,6 +928,13 @@ var TerminalClient = class {
|
|
|
902
928
|
this.spawnReject = null;
|
|
903
929
|
}
|
|
904
930
|
break;
|
|
931
|
+
case "serverInfo":
|
|
932
|
+
this.serverInfo = message.info;
|
|
933
|
+
this.serverInfoHandlers.forEach((handler) => handler(message.info));
|
|
934
|
+
break;
|
|
935
|
+
case "containerList":
|
|
936
|
+
this.containerListHandlers.forEach((handler) => handler(message.containers));
|
|
937
|
+
break;
|
|
905
938
|
}
|
|
906
939
|
}
|
|
907
940
|
/**
|
|
@@ -1026,6 +1059,31 @@ var TerminalClient = class {
|
|
|
1026
1059
|
onSpawned(handler) {
|
|
1027
1060
|
this.spawnedHandlers.push(handler);
|
|
1028
1061
|
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Called when server info is received
|
|
1064
|
+
*/
|
|
1065
|
+
onServerInfo(handler) {
|
|
1066
|
+
this.serverInfoHandlers.push(handler);
|
|
1067
|
+
if (this.serverInfo) {
|
|
1068
|
+
handler(this.serverInfo);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Called when container list is received
|
|
1073
|
+
*/
|
|
1074
|
+
onContainerList(handler) {
|
|
1075
|
+
this.containerListHandlers.push(handler);
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Request list of available containers
|
|
1079
|
+
*/
|
|
1080
|
+
requestContainerList() {
|
|
1081
|
+
if (!this.ws || this.state !== "connected") {
|
|
1082
|
+
console.error("[x-shell] Cannot request containers: not connected");
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
this.ws.send(JSON.stringify({ type: "listContainers" }));
|
|
1086
|
+
}
|
|
1029
1087
|
// ==========================================
|
|
1030
1088
|
// Getters
|
|
1031
1089
|
// ==========================================
|
|
@@ -1059,6 +1117,12 @@ var TerminalClient = class {
|
|
|
1059
1117
|
hasActiveSession() {
|
|
1060
1118
|
return this.sessionId !== null;
|
|
1061
1119
|
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Get server info
|
|
1122
|
+
*/
|
|
1123
|
+
getServerInfo() {
|
|
1124
|
+
return this.serverInfo;
|
|
1125
|
+
}
|
|
1062
1126
|
};
|
|
1063
1127
|
|
|
1064
1128
|
// src/ui/x-shell-terminal.ts
|
|
@@ -1074,6 +1138,13 @@ var XShellTerminal = class extends i4 {
|
|
|
1074
1138
|
this.noHeader = false;
|
|
1075
1139
|
this.autoConnect = false;
|
|
1076
1140
|
this.autoSpawn = false;
|
|
1141
|
+
this.container = "";
|
|
1142
|
+
this.containerShell = "";
|
|
1143
|
+
this.containerUser = "";
|
|
1144
|
+
this.containerCwd = "";
|
|
1145
|
+
this.showConnectionPanel = false;
|
|
1146
|
+
this.showSettings = false;
|
|
1147
|
+
this.showStatusBar = false;
|
|
1077
1148
|
this.fontSize = 14;
|
|
1078
1149
|
this.fontFamily = 'Menlo, Monaco, "Courier New", monospace';
|
|
1079
1150
|
this.client = null;
|
|
@@ -1084,6 +1155,14 @@ var XShellTerminal = class extends i4 {
|
|
|
1084
1155
|
this.loading = false;
|
|
1085
1156
|
this.error = null;
|
|
1086
1157
|
this.sessionInfo = null;
|
|
1158
|
+
this.containers = [];
|
|
1159
|
+
this.serverInfo = null;
|
|
1160
|
+
this.selectedContainer = "";
|
|
1161
|
+
this.selectedShell = "/bin/sh";
|
|
1162
|
+
this.connectionMode = "docker";
|
|
1163
|
+
this.settingsMenuOpen = false;
|
|
1164
|
+
this.statusMessage = "";
|
|
1165
|
+
this.statusType = "info";
|
|
1087
1166
|
// xterm.js module (loaded dynamically)
|
|
1088
1167
|
this.xtermModule = null;
|
|
1089
1168
|
this.fitAddonModule = null;
|
|
@@ -1116,6 +1195,26 @@ var XShellTerminal = class extends i4 {
|
|
|
1116
1195
|
throw new Error("Failed to load xterm.js. Make sure it is available.");
|
|
1117
1196
|
}
|
|
1118
1197
|
}
|
|
1198
|
+
await this.injectXtermCSS();
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Inject xterm.js CSS into shadow DOM
|
|
1202
|
+
*/
|
|
1203
|
+
async injectXtermCSS() {
|
|
1204
|
+
if (!this.shadowRoot)
|
|
1205
|
+
return;
|
|
1206
|
+
if (this.shadowRoot.querySelector("#xterm-styles"))
|
|
1207
|
+
return;
|
|
1208
|
+
try {
|
|
1209
|
+
const response = await fetch("https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css");
|
|
1210
|
+
const css = await response.text();
|
|
1211
|
+
const style = document.createElement("style");
|
|
1212
|
+
style.id = "xterm-styles";
|
|
1213
|
+
style.textContent = css;
|
|
1214
|
+
this.shadowRoot.prepend(style);
|
|
1215
|
+
} catch (e5) {
|
|
1216
|
+
console.warn("[x-shell] Failed to load xterm CSS:", e5);
|
|
1217
|
+
}
|
|
1119
1218
|
}
|
|
1120
1219
|
/**
|
|
1121
1220
|
* Connect to the terminal server
|
|
@@ -1144,6 +1243,7 @@ var XShellTerminal = class extends i4 {
|
|
|
1144
1243
|
});
|
|
1145
1244
|
this.client.onError((err) => {
|
|
1146
1245
|
this.error = err.message;
|
|
1246
|
+
this.setStatus(err.message, "error");
|
|
1147
1247
|
this.dispatchEvent(
|
|
1148
1248
|
new CustomEvent("error", { detail: { error: err }, bubbles: true, composed: true })
|
|
1149
1249
|
);
|
|
@@ -1166,10 +1266,25 @@ var XShellTerminal = class extends i4 {
|
|
|
1166
1266
|
});
|
|
1167
1267
|
this.client.onSpawned((info) => {
|
|
1168
1268
|
this.sessionInfo = info;
|
|
1269
|
+
this.setStatus(`Session started: ${info.container || info.shell}`, "success");
|
|
1169
1270
|
this.dispatchEvent(
|
|
1170
1271
|
new CustomEvent("spawned", { detail: { session: info }, bubbles: true, composed: true })
|
|
1171
1272
|
);
|
|
1172
1273
|
});
|
|
1274
|
+
this.client.onServerInfo((info) => {
|
|
1275
|
+
this.serverInfo = info;
|
|
1276
|
+
if (info.dockerEnabled) {
|
|
1277
|
+
this.connectionMode = "docker";
|
|
1278
|
+
this.client?.requestContainerList();
|
|
1279
|
+
}
|
|
1280
|
+
this.selectedShell = info.defaultShell;
|
|
1281
|
+
});
|
|
1282
|
+
this.client.onContainerList((containers) => {
|
|
1283
|
+
this.containers = containers;
|
|
1284
|
+
if (containers.length > 0 && !this.selectedContainer) {
|
|
1285
|
+
this.selectedContainer = containers[0].name;
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1173
1288
|
await this.client.connect();
|
|
1174
1289
|
} catch (err) {
|
|
1175
1290
|
this.error = err instanceof Error ? err.message : "Connection failed";
|
|
@@ -1204,7 +1319,12 @@ var XShellTerminal = class extends i4 {
|
|
|
1204
1319
|
cwd: options?.cwd || this.cwd || void 0,
|
|
1205
1320
|
cols: this.terminal?.cols || this.cols,
|
|
1206
1321
|
rows: this.terminal?.rows || this.rows,
|
|
1207
|
-
env: options?.env
|
|
1322
|
+
env: options?.env,
|
|
1323
|
+
// Docker container options
|
|
1324
|
+
container: options?.container || this.container || void 0,
|
|
1325
|
+
containerShell: options?.containerShell || this.containerShell || void 0,
|
|
1326
|
+
containerUser: options?.containerUser || this.containerUser || void 0,
|
|
1327
|
+
containerCwd: options?.containerCwd || this.containerCwd || void 0
|
|
1208
1328
|
};
|
|
1209
1329
|
const info = await this.client.spawn(spawnOptions);
|
|
1210
1330
|
this.sessionActive = true;
|
|
@@ -1212,8 +1332,10 @@ var XShellTerminal = class extends i4 {
|
|
|
1212
1332
|
if (this.terminal) {
|
|
1213
1333
|
this.terminal.focus();
|
|
1214
1334
|
}
|
|
1335
|
+
return info;
|
|
1215
1336
|
} catch (err) {
|
|
1216
1337
|
this.error = err instanceof Error ? err.message : "Failed to spawn session";
|
|
1338
|
+
throw err;
|
|
1217
1339
|
} finally {
|
|
1218
1340
|
this.loading = false;
|
|
1219
1341
|
}
|
|
@@ -1267,19 +1389,27 @@ var XShellTerminal = class extends i4 {
|
|
|
1267
1389
|
* Get terminal theme based on component theme
|
|
1268
1390
|
*/
|
|
1269
1391
|
getTerminalTheme() {
|
|
1270
|
-
|
|
1392
|
+
let effectiveTheme = this.theme;
|
|
1393
|
+
if (this.theme === "auto") {
|
|
1394
|
+
effectiveTheme = window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
|
|
1395
|
+
}
|
|
1396
|
+
if (effectiveTheme === "light") {
|
|
1271
1397
|
return {
|
|
1272
1398
|
background: "#ffffff",
|
|
1273
1399
|
foreground: "#1f2937",
|
|
1274
1400
|
cursor: "#1f2937",
|
|
1275
|
-
|
|
1401
|
+
cursorAccent: "#ffffff",
|
|
1402
|
+
selection: "#b4d5fe",
|
|
1403
|
+
selectionForeground: "#1f2937"
|
|
1276
1404
|
};
|
|
1277
1405
|
}
|
|
1278
1406
|
return {
|
|
1279
1407
|
background: "#1e1e1e",
|
|
1280
1408
|
foreground: "#cccccc",
|
|
1281
1409
|
cursor: "#ffffff",
|
|
1282
|
-
|
|
1410
|
+
cursorAccent: "#1e1e1e",
|
|
1411
|
+
selection: "#264f78",
|
|
1412
|
+
selectionForeground: "#ffffff"
|
|
1283
1413
|
};
|
|
1284
1414
|
}
|
|
1285
1415
|
/**
|
|
@@ -1342,6 +1472,245 @@ var XShellTerminal = class extends i4 {
|
|
|
1342
1472
|
}
|
|
1343
1473
|
this.fitAddon = null;
|
|
1344
1474
|
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Set status message
|
|
1477
|
+
*/
|
|
1478
|
+
setStatus(message, type = "info") {
|
|
1479
|
+
this.statusMessage = message;
|
|
1480
|
+
this.statusType = type;
|
|
1481
|
+
if (type !== "error") {
|
|
1482
|
+
setTimeout(() => {
|
|
1483
|
+
if (this.statusMessage === message) {
|
|
1484
|
+
this.statusMessage = "";
|
|
1485
|
+
}
|
|
1486
|
+
}, 5e3);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Clear status message
|
|
1491
|
+
*/
|
|
1492
|
+
clearStatus() {
|
|
1493
|
+
this.statusMessage = "";
|
|
1494
|
+
this.statusType = "info";
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Handle theme change
|
|
1498
|
+
*/
|
|
1499
|
+
handleThemeChange(e5) {
|
|
1500
|
+
const select = e5.target;
|
|
1501
|
+
this.theme = select.value;
|
|
1502
|
+
this.applyTerminalTheme();
|
|
1503
|
+
this.dispatchEvent(new CustomEvent("theme-change", {
|
|
1504
|
+
detail: { theme: this.theme },
|
|
1505
|
+
bubbles: true,
|
|
1506
|
+
composed: true
|
|
1507
|
+
}));
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Apply current theme to xterm.js terminal
|
|
1511
|
+
*/
|
|
1512
|
+
applyTerminalTheme() {
|
|
1513
|
+
if (!this.terminal)
|
|
1514
|
+
return;
|
|
1515
|
+
const terminalTheme = this.getTerminalTheme();
|
|
1516
|
+
this.terminal.options.theme = terminalTheme;
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* Apply current font size to xterm.js terminal
|
|
1520
|
+
*/
|
|
1521
|
+
applyTerminalFontSize() {
|
|
1522
|
+
if (!this.terminal)
|
|
1523
|
+
return;
|
|
1524
|
+
this.terminal.options.fontSize = this.fontSize;
|
|
1525
|
+
if (this.fitAddon) {
|
|
1526
|
+
this.fitAddon.fit();
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Handle connection mode change
|
|
1531
|
+
*/
|
|
1532
|
+
handleModeChange(e5) {
|
|
1533
|
+
const select = e5.target;
|
|
1534
|
+
this.connectionMode = select.value;
|
|
1535
|
+
if (this.connectionMode === "docker" && this.client && this.connected) {
|
|
1536
|
+
this.client.requestContainerList();
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Handle connect from connection panel
|
|
1541
|
+
*/
|
|
1542
|
+
async handlePanelConnect() {
|
|
1543
|
+
if (!this.connected) {
|
|
1544
|
+
await this.connect();
|
|
1545
|
+
}
|
|
1546
|
+
if (this.connected) {
|
|
1547
|
+
const options = {};
|
|
1548
|
+
if (this.connectionMode === "docker" && this.selectedContainer) {
|
|
1549
|
+
options.container = this.selectedContainer;
|
|
1550
|
+
options.containerShell = this.selectedShell || "/bin/sh";
|
|
1551
|
+
} else {
|
|
1552
|
+
options.shell = this.selectedShell || void 0;
|
|
1553
|
+
}
|
|
1554
|
+
await this.spawn(options);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Toggle settings menu
|
|
1559
|
+
*/
|
|
1560
|
+
toggleSettingsMenu() {
|
|
1561
|
+
this.settingsMenuOpen = !this.settingsMenuOpen;
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* Render connection panel
|
|
1565
|
+
*/
|
|
1566
|
+
renderConnectionPanel() {
|
|
1567
|
+
if (!this.showConnectionPanel)
|
|
1568
|
+
return A;
|
|
1569
|
+
const runningContainers = this.containers.filter((c4) => c4.state === "running");
|
|
1570
|
+
return b2`
|
|
1571
|
+
<div class="connection-panel">
|
|
1572
|
+
<div class="connection-panel-title">
|
|
1573
|
+
<span>Connection</span>
|
|
1574
|
+
${this.serverInfo?.dockerEnabled ? b2`<span style="font-size: 11px; color: var(--xs-status-connected);">Docker enabled</span>` : A}
|
|
1575
|
+
</div>
|
|
1576
|
+
<div class="connection-form">
|
|
1577
|
+
<div class="form-group">
|
|
1578
|
+
<label>Mode</label>
|
|
1579
|
+
<select
|
|
1580
|
+
.value=${this.connectionMode}
|
|
1581
|
+
@change=${this.handleModeChange}
|
|
1582
|
+
?disabled=${this.sessionActive}
|
|
1583
|
+
>
|
|
1584
|
+
<option value="local">Local Shell</option>
|
|
1585
|
+
${this.serverInfo?.dockerEnabled ? b2`<option value="docker">Docker Container</option>` : A}
|
|
1586
|
+
</select>
|
|
1587
|
+
</div>
|
|
1588
|
+
|
|
1589
|
+
${this.connectionMode === "docker" ? b2`
|
|
1590
|
+
<div class="form-group">
|
|
1591
|
+
<label>Container</label>
|
|
1592
|
+
<select
|
|
1593
|
+
.value=${this.selectedContainer}
|
|
1594
|
+
@change=${(e5) => this.selectedContainer = e5.target.value}
|
|
1595
|
+
?disabled=${this.sessionActive}
|
|
1596
|
+
>
|
|
1597
|
+
${runningContainers.length === 0 ? b2`<option value="">No containers running</option>` : runningContainers.map((c4) => b2`
|
|
1598
|
+
<option value=${c4.name}>${c4.name} (${c4.image})</option>
|
|
1599
|
+
`)}
|
|
1600
|
+
</select>
|
|
1601
|
+
</div>
|
|
1602
|
+
` : A}
|
|
1603
|
+
|
|
1604
|
+
<div class="form-group">
|
|
1605
|
+
<label>Shell</label>
|
|
1606
|
+
<select
|
|
1607
|
+
.value=${this.selectedShell}
|
|
1608
|
+
@change=${(e5) => this.selectedShell = e5.target.value}
|
|
1609
|
+
?disabled=${this.sessionActive}
|
|
1610
|
+
>
|
|
1611
|
+
${this.serverInfo?.allowedShells.length ? this.serverInfo.allowedShells.map((s4) => b2`<option value=${s4}>${s4}</option>`) : b2`
|
|
1612
|
+
<option value="/bin/bash">/bin/bash</option>
|
|
1613
|
+
<option value="/bin/sh">/bin/sh</option>
|
|
1614
|
+
<option value="/bin/zsh">/bin/zsh</option>
|
|
1615
|
+
`}
|
|
1616
|
+
</select>
|
|
1617
|
+
</div>
|
|
1618
|
+
|
|
1619
|
+
<div class="form-group">
|
|
1620
|
+
${!this.connected ? b2`<button class="btn-primary" @click=${this.handlePanelConnect} ?disabled=${this.loading}>
|
|
1621
|
+
${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"}
|
|
1624
|
+
</button>` : b2`<button class="btn-danger" @click=${this.kill}>
|
|
1625
|
+
Stop Session
|
|
1626
|
+
</button>`}
|
|
1627
|
+
</div>
|
|
1628
|
+
</div>
|
|
1629
|
+
</div>
|
|
1630
|
+
`;
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Render settings dropdown
|
|
1634
|
+
*/
|
|
1635
|
+
renderSettingsDropdown() {
|
|
1636
|
+
if (!this.showSettings)
|
|
1637
|
+
return A;
|
|
1638
|
+
return b2`
|
|
1639
|
+
<div class="settings-dropdown">
|
|
1640
|
+
<button @click=${this.toggleSettingsMenu} title="Settings">
|
|
1641
|
+
⚙️
|
|
1642
|
+
</button>
|
|
1643
|
+
${this.settingsMenuOpen ? b2`
|
|
1644
|
+
<div class="settings-menu">
|
|
1645
|
+
<div class="settings-menu-item">
|
|
1646
|
+
<span>Theme</span>
|
|
1647
|
+
<select
|
|
1648
|
+
.value=${this.theme}
|
|
1649
|
+
@change=${this.handleThemeChange}
|
|
1650
|
+
>
|
|
1651
|
+
<option value="dark">Dark</option>
|
|
1652
|
+
<option value="light">Light</option>
|
|
1653
|
+
<option value="auto">Auto</option>
|
|
1654
|
+
</select>
|
|
1655
|
+
</div>
|
|
1656
|
+
<div class="settings-divider"></div>
|
|
1657
|
+
<div class="settings-menu-item">
|
|
1658
|
+
<span>Font Size</span>
|
|
1659
|
+
<select
|
|
1660
|
+
.value=${String(this.fontSize)}
|
|
1661
|
+
@change=${(e5) => {
|
|
1662
|
+
this.fontSize = parseInt(e5.target.value);
|
|
1663
|
+
this.applyTerminalFontSize();
|
|
1664
|
+
}}
|
|
1665
|
+
>
|
|
1666
|
+
<option value="12">12px</option>
|
|
1667
|
+
<option value="14">14px</option>
|
|
1668
|
+
<option value="16">16px</option>
|
|
1669
|
+
<option value="18">18px</option>
|
|
1670
|
+
</select>
|
|
1671
|
+
</div>
|
|
1672
|
+
<div class="settings-divider"></div>
|
|
1673
|
+
<div class="settings-menu-item" @click=${this.clear}>
|
|
1674
|
+
<span>Clear Terminal</span>
|
|
1675
|
+
</div>
|
|
1676
|
+
</div>
|
|
1677
|
+
` : A}
|
|
1678
|
+
</div>
|
|
1679
|
+
`;
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Render status bar
|
|
1683
|
+
*/
|
|
1684
|
+
renderStatusBar() {
|
|
1685
|
+
if (!this.showStatusBar)
|
|
1686
|
+
return A;
|
|
1687
|
+
return b2`
|
|
1688
|
+
<div class="status-bar">
|
|
1689
|
+
<div class="status-bar-left">
|
|
1690
|
+
<span class="status-dot ${this.connected ? "connected" : ""}"></span>
|
|
1691
|
+
<span>${this.connected ? this.sessionActive ? "Session active" : "Connected" : "Disconnected"}</span>
|
|
1692
|
+
${this.sessionInfo ? b2`
|
|
1693
|
+
<span style="color: var(--xs-text-muted)">|</span>
|
|
1694
|
+
<span>${this.sessionInfo.container || this.sessionInfo.shell}</span>
|
|
1695
|
+
<span style="color: var(--xs-text-muted)">${this.sessionInfo.cols}x${this.sessionInfo.rows}</span>
|
|
1696
|
+
` : A}
|
|
1697
|
+
</div>
|
|
1698
|
+
<div class="status-bar-right">
|
|
1699
|
+
${this.statusMessage ? b2`
|
|
1700
|
+
<span class="${this.statusType === "error" ? "status-bar-error" : this.statusType === "success" ? "status-bar-success" : ""}">
|
|
1701
|
+
${this.statusType === "error" ? "\u26A0\uFE0F" : this.statusType === "success" ? "\u2713" : ""}
|
|
1702
|
+
${this.statusMessage}
|
|
1703
|
+
</span>
|
|
1704
|
+
<button
|
|
1705
|
+
style="background: none; border: none; cursor: pointer; padding: 0; font-size: 10px;"
|
|
1706
|
+
@click=${this.clearStatus}
|
|
1707
|
+
title="Dismiss"
|
|
1708
|
+
>✕</button>
|
|
1709
|
+
` : A}
|
|
1710
|
+
</div>
|
|
1711
|
+
</div>
|
|
1712
|
+
`;
|
|
1713
|
+
}
|
|
1345
1714
|
render() {
|
|
1346
1715
|
return b2`
|
|
1347
1716
|
${this.noHeader ? A : b2`
|
|
@@ -1349,27 +1718,36 @@ var XShellTerminal = class extends i4 {
|
|
|
1349
1718
|
<div class="header-title">
|
|
1350
1719
|
<span>Terminal</span>
|
|
1351
1720
|
${this.sessionInfo ? b2`<span style="font-weight: normal; font-size: 12px; color: var(--xs-text-muted)">
|
|
1352
|
-
${this.sessionInfo.shell}
|
|
1721
|
+
${this.sessionInfo.container ? `${this.sessionInfo.container} (${this.sessionInfo.shell})` : this.sessionInfo.shell}
|
|
1353
1722
|
</span>` : A}
|
|
1354
1723
|
</div>
|
|
1355
1724
|
<div class="header-actions">
|
|
1356
|
-
${!this.
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1725
|
+
${!this.showConnectionPanel ? b2`
|
|
1726
|
+
${!this.connected ? b2`<button @click=${this.connect} ?disabled=${this.loading}>
|
|
1727
|
+
${this.loading ? "Connecting..." : "Connect"}
|
|
1728
|
+
</button>` : !this.sessionActive ? b2`<button @click=${() => this.spawn()} ?disabled=${this.loading}>
|
|
1729
|
+
${this.loading ? "Spawning..." : "Start"}
|
|
1730
|
+
</button>` : b2`<button @click=${this.kill}>Stop</button>`}
|
|
1731
|
+
` : A}
|
|
1361
1732
|
<button @click=${this.clear} ?disabled=${!this.sessionActive}>Clear</button>
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
<
|
|
1365
|
-
|
|
1733
|
+
${this.renderSettingsDropdown()}
|
|
1734
|
+
${!this.showStatusBar ? b2`
|
|
1735
|
+
<div class="status">
|
|
1736
|
+
<span class="status-dot ${this.connected ? "connected" : ""}"></span>
|
|
1737
|
+
<span>${this.connected ? "Connected" : "Disconnected"}</span>
|
|
1738
|
+
</div>
|
|
1739
|
+
` : A}
|
|
1366
1740
|
</div>
|
|
1367
1741
|
</div>
|
|
1368
1742
|
`}
|
|
1369
1743
|
|
|
1744
|
+
${this.renderConnectionPanel()}
|
|
1745
|
+
|
|
1370
1746
|
<div class="terminal-container">
|
|
1371
1747
|
${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}
|
|
1372
1748
|
</div>
|
|
1749
|
+
|
|
1750
|
+
${this.renderStatusBar()}
|
|
1373
1751
|
`;
|
|
1374
1752
|
}
|
|
1375
1753
|
};
|
|
@@ -1476,6 +1854,139 @@ XShellTerminal.styles = [
|
|
|
1476
1854
|
:host([no-header]) .header {
|
|
1477
1855
|
display: none;
|
|
1478
1856
|
}
|
|
1857
|
+
|
|
1858
|
+
/* Connection panel */
|
|
1859
|
+
.connection-panel {
|
|
1860
|
+
padding: 12px;
|
|
1861
|
+
background: var(--xs-bg-header);
|
|
1862
|
+
border-bottom: 1px solid var(--xs-border);
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
.connection-panel-title {
|
|
1866
|
+
font-weight: 600;
|
|
1867
|
+
margin-bottom: 12px;
|
|
1868
|
+
display: flex;
|
|
1869
|
+
align-items: center;
|
|
1870
|
+
gap: 8px;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
.connection-form {
|
|
1874
|
+
display: grid;
|
|
1875
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
1876
|
+
gap: 10px;
|
|
1877
|
+
align-items: end;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
.form-group {
|
|
1881
|
+
display: flex;
|
|
1882
|
+
flex-direction: column;
|
|
1883
|
+
gap: 4px;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
.form-group label {
|
|
1887
|
+
font-size: 11px;
|
|
1888
|
+
text-transform: uppercase;
|
|
1889
|
+
color: var(--xs-text-muted);
|
|
1890
|
+
letter-spacing: 0.5px;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
.form-group select,
|
|
1894
|
+
.form-group input {
|
|
1895
|
+
padding: 6px 10px;
|
|
1896
|
+
border: 1px solid var(--xs-border);
|
|
1897
|
+
border-radius: 4px;
|
|
1898
|
+
background: var(--xs-bg);
|
|
1899
|
+
color: var(--xs-text);
|
|
1900
|
+
font-size: 13px;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
.form-group select:focus,
|
|
1904
|
+
.form-group input:focus {
|
|
1905
|
+
outline: none;
|
|
1906
|
+
border-color: var(--xs-status-connected);
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
/* Settings dropdown */
|
|
1910
|
+
.settings-dropdown {
|
|
1911
|
+
position: relative;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
.settings-menu {
|
|
1915
|
+
position: absolute;
|
|
1916
|
+
top: 100%;
|
|
1917
|
+
right: 0;
|
|
1918
|
+
margin-top: 4px;
|
|
1919
|
+
min-width: 180px;
|
|
1920
|
+
background: var(--xs-bg-header);
|
|
1921
|
+
border: 1px solid var(--xs-border);
|
|
1922
|
+
border-radius: 4px;
|
|
1923
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|
1924
|
+
z-index: 100;
|
|
1925
|
+
padding: 8px 0;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
.settings-menu-item {
|
|
1929
|
+
display: flex;
|
|
1930
|
+
align-items: center;
|
|
1931
|
+
justify-content: space-between;
|
|
1932
|
+
padding: 8px 12px;
|
|
1933
|
+
font-size: 13px;
|
|
1934
|
+
cursor: pointer;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
.settings-menu-item:hover {
|
|
1938
|
+
background: var(--xs-btn-hover);
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
.settings-menu-item select {
|
|
1942
|
+
padding: 4px 8px;
|
|
1943
|
+
border: 1px solid var(--xs-border);
|
|
1944
|
+
border-radius: 3px;
|
|
1945
|
+
background: var(--xs-bg);
|
|
1946
|
+
color: var(--xs-text);
|
|
1947
|
+
font-size: 12px;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
.settings-divider {
|
|
1951
|
+
height: 1px;
|
|
1952
|
+
background: var(--xs-border);
|
|
1953
|
+
margin: 4px 0;
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
/* Status bar */
|
|
1957
|
+
.status-bar {
|
|
1958
|
+
display: flex;
|
|
1959
|
+
align-items: center;
|
|
1960
|
+
justify-content: space-between;
|
|
1961
|
+
padding: 4px 12px;
|
|
1962
|
+
background: var(--xs-bg-header);
|
|
1963
|
+
border-top: 1px solid var(--xs-border);
|
|
1964
|
+
font-size: 12px;
|
|
1965
|
+
color: var(--xs-text-muted);
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
.status-bar-left {
|
|
1969
|
+
display: flex;
|
|
1970
|
+
align-items: center;
|
|
1971
|
+
gap: 12px;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
.status-bar-right {
|
|
1975
|
+
display: flex;
|
|
1976
|
+
align-items: center;
|
|
1977
|
+
gap: 8px;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
.status-bar-error {
|
|
1981
|
+
color: #ef4444;
|
|
1982
|
+
display: flex;
|
|
1983
|
+
align-items: center;
|
|
1984
|
+
gap: 4px;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
.status-bar-success {
|
|
1988
|
+
color: var(--xs-status-connected);
|
|
1989
|
+
}
|
|
1479
1990
|
`
|
|
1480
1991
|
];
|
|
1481
1992
|
__decorateClass([
|
|
@@ -1505,6 +2016,27 @@ __decorateClass([
|
|
|
1505
2016
|
__decorateClass([
|
|
1506
2017
|
n4({ type: Boolean, attribute: "auto-spawn" })
|
|
1507
2018
|
], XShellTerminal.prototype, "autoSpawn", 2);
|
|
2019
|
+
__decorateClass([
|
|
2020
|
+
n4({ type: String })
|
|
2021
|
+
], XShellTerminal.prototype, "container", 2);
|
|
2022
|
+
__decorateClass([
|
|
2023
|
+
n4({ type: String, attribute: "container-shell" })
|
|
2024
|
+
], XShellTerminal.prototype, "containerShell", 2);
|
|
2025
|
+
__decorateClass([
|
|
2026
|
+
n4({ type: String, attribute: "container-user" })
|
|
2027
|
+
], XShellTerminal.prototype, "containerUser", 2);
|
|
2028
|
+
__decorateClass([
|
|
2029
|
+
n4({ type: String, attribute: "container-cwd" })
|
|
2030
|
+
], XShellTerminal.prototype, "containerCwd", 2);
|
|
2031
|
+
__decorateClass([
|
|
2032
|
+
n4({ type: Boolean, attribute: "show-connection-panel" })
|
|
2033
|
+
], XShellTerminal.prototype, "showConnectionPanel", 2);
|
|
2034
|
+
__decorateClass([
|
|
2035
|
+
n4({ type: Boolean, attribute: "show-settings" })
|
|
2036
|
+
], XShellTerminal.prototype, "showSettings", 2);
|
|
2037
|
+
__decorateClass([
|
|
2038
|
+
n4({ type: Boolean, attribute: "show-status-bar" })
|
|
2039
|
+
], XShellTerminal.prototype, "showStatusBar", 2);
|
|
1508
2040
|
__decorateClass([
|
|
1509
2041
|
n4({ type: Number, attribute: "font-size" })
|
|
1510
2042
|
], XShellTerminal.prototype, "fontSize", 2);
|
|
@@ -1535,6 +2067,30 @@ __decorateClass([
|
|
|
1535
2067
|
__decorateClass([
|
|
1536
2068
|
r5()
|
|
1537
2069
|
], XShellTerminal.prototype, "sessionInfo", 2);
|
|
2070
|
+
__decorateClass([
|
|
2071
|
+
r5()
|
|
2072
|
+
], XShellTerminal.prototype, "containers", 2);
|
|
2073
|
+
__decorateClass([
|
|
2074
|
+
r5()
|
|
2075
|
+
], XShellTerminal.prototype, "serverInfo", 2);
|
|
2076
|
+
__decorateClass([
|
|
2077
|
+
r5()
|
|
2078
|
+
], XShellTerminal.prototype, "selectedContainer", 2);
|
|
2079
|
+
__decorateClass([
|
|
2080
|
+
r5()
|
|
2081
|
+
], XShellTerminal.prototype, "selectedShell", 2);
|
|
2082
|
+
__decorateClass([
|
|
2083
|
+
r5()
|
|
2084
|
+
], XShellTerminal.prototype, "connectionMode", 2);
|
|
2085
|
+
__decorateClass([
|
|
2086
|
+
r5()
|
|
2087
|
+
], XShellTerminal.prototype, "settingsMenuOpen", 2);
|
|
2088
|
+
__decorateClass([
|
|
2089
|
+
r5()
|
|
2090
|
+
], XShellTerminal.prototype, "statusMessage", 2);
|
|
2091
|
+
__decorateClass([
|
|
2092
|
+
r5()
|
|
2093
|
+
], XShellTerminal.prototype, "statusType", 2);
|
|
1538
2094
|
XShellTerminal = __decorateClass([
|
|
1539
2095
|
t3("x-shell-terminal")
|
|
1540
2096
|
], XShellTerminal);
|