Skip to content

Commit 5df5ca3

Browse files
authored
Merge pull request #24 from chechojgb/terminal
refactor: autocompletar comando terminal ssh
2 parents 3eaf2c1 + b8939fb commit 5df5ca3

File tree

6 files changed

+305
-227
lines changed

6 files changed

+305
-227
lines changed

microservicio-ssh/index.js

Lines changed: 107 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,112 @@
1-
require('dotenv').config();
2-
const express = require('express');
3-
const http = require('http');
4-
const { WebSocketServer } = require('ws');
5-
const { Client } = require('ssh2');
1+
// ✅ Backend index.js actualizado (autocompletado y sugerencias con logs extendidos)
2+
3+
require("dotenv").config();
4+
const express = require("express");
5+
const http = require("http");
6+
const { WebSocketServer } = require("ws");
7+
const { Client } = require("ssh2");
68

79
const PORT = process.env.PORT || 3001;
810
const app = express();
911
const server = http.createServer(app);
1012
const wss = new WebSocketServer({ server });
1113

12-
wss.on('connection', (ws) => {
13-
console.log('📡 Cliente conectado');
14-
ws.send(JSON.stringify({ status: '🟢 Consola lista para conectar' }));
14+
wss.on("connection", (ws) => {
15+
console.log("📡 Cliente conectado");
16+
ws.send(JSON.stringify({ status: "🟢 Consola lista para conectar" }));
1517

1618
const ssh = new Client();
1719
let shellStream;
18-
let contextBuffer = '';
20+
let contextBuffer = "";
1921
let collectingContext = false;
2022

2123
const cleanContextPart = (text) => {
2224
return text
23-
.replace(/\x1B\[[0-9;?]*[a-zA-Z]/g, '')
24-
.split('\n')
25+
.replace(/\x1B\[[0-9;?]*[a-zA-Z]/g, "")
26+
.split("\n")
2527
.map((line) => line.trim())
26-
.filter((line) => line && !line.includes('__CTX') && !line.includes('$'))
27-
.join('')
28+
.filter((line) => line && !line.includes("__CTX") && !line.includes("$"))
29+
.join("")
2830
.trim();
2931
};
3032

3133
ssh
32-
.on('ready', () => {
33-
console.log('🔐 Conexión SSH establecida');
34+
.on("ready", () => {
35+
console.log("🔐 Conexión SSH establecida");
3436
ssh.shell((err, stream) => {
3537
if (err) {
36-
ws.send(JSON.stringify({ error: 'Error iniciando shell: ' + err.message }));
38+
ws.send(JSON.stringify({ error: "Error iniciando shell: " + err.message }));
3739
return;
3840
}
3941

4042
shellStream = stream;
41-
stream.write('echo __CTX_START__ && whoami && echo __CTX__ && hostname && echo __CTX__ && pwd && echo __CTX_END__\n');
43+
stream.write(
44+
"echo __CTX_START__ && whoami && echo __CTX__ && hostname && echo __CTX__ && pwd && echo __CTX_END__\n"
45+
);
4246

43-
stream.on('data', (data) => {
47+
stream.on("data", (data) => {
4448
const text = data.toString();
45-
46-
if (text.includes('__AUTO_MARK_') && text.includes('__END')) {
47-
const matched = text.match(/__AUTO_MARK_(\d+)__/);
48-
const id = matched?.[1];
49-
if (!id) return;
50-
51-
const [_, raw] = text.split(`__AUTO_MARK_${id}__`);
52-
const [content] = raw.split(`__AUTO_MARK_${id}___END`);
53-
54-
const lines = content
55-
.split('\n')
56-
.map((l) => l.trim())
57-
.filter(Boolean);
58-
59-
const unique = [...new Set(lines)];
60-
if (unique.length === 1) {
61-
ws.send(JSON.stringify({ autocomplete: unique[0] }));
62-
} else if (unique.length > 1) {
63-
ws.send(JSON.stringify({ suggestions: unique }));
64-
}
65-
return;
49+
console.log("📥 SSH DATA:", JSON.stringify(text));
50+
51+
if (text.includes("__AUTO_MARK_") && text.includes("__END")) {
52+
const matched = text.match(/__AUTO_MARK_(\d+)__/);
53+
const id = matched?.[1];
54+
if (!id) return;
55+
56+
const [_, raw] = text.split(`__AUTO_MARK_${id}__`);
57+
const [content] = raw.split(`__AUTO_MARK_${id}___END`);
58+
59+
const rawLines = content.split("\n").map((l) => l.trim());
60+
61+
const cleanLine = (line) =>
62+
line
63+
.replace(/\x1B\[[0-9;?]*[a-zA-Z]/g, "") // ANSI codes
64+
.replace(/\x1B\][^\x07]*\x07/g, "") // OSC sequences
65+
.replace(/^\s*[\x00-\x1F\x7F-\x9F]+/, "") // Control chars
66+
.trim();
67+
68+
const lines = rawLines
69+
.map(cleanLine)
70+
.filter(
71+
(l) =>
72+
l &&
73+
!l.includes("__AUTO") &&
74+
!l.includes("_END") &&
75+
!/^\w+@[\w.-]+:.*\$/.test(l) &&
76+
!l.startsWith("echo") &&
77+
!l.includes("compgen") &&
78+
!l.startsWith("bash:") &&
79+
!/^\x1B/.test(l)
80+
);
81+
82+
const unique = [...new Set(lines)];
83+
84+
console.log("🔍 Sugerencias/autocompletado recibidas:", unique);
85+
86+
if (unique.length > 0) {
87+
ws.send(JSON.stringify({ suggestions: unique }));
88+
// También opcionalmente las imprimes en la terminal
89+
// shellStream.write("echo " + unique.join(" ") + "\n");
90+
} else {
91+
ws.send(JSON.stringify({ suggestions: [] }));
92+
}
93+
return;
6694
}
6795

68-
if (text.includes('__CTX_START__')) {
96+
if (text.includes("__CTX_START__")) {
6997
collectingContext = true;
70-
contextBuffer = '';
98+
contextBuffer = "";
7199
return;
72100
}
73101

74-
if (text.includes('__CTX_END__')) {
102+
if (text.includes("__CTX_END__")) {
75103
collectingContext = false;
76104
contextBuffer += text;
77-
const parts = contextBuffer.split('__CTX__');
105+
const parts = contextBuffer.split("__CTX__");
78106
const context = {
79-
user: cleanContextPart(parts[0] || ''),
80-
host: cleanContextPart(parts[1] || ''),
81-
cwd: cleanContextPart(parts[2] || ''),
107+
user: cleanContextPart(parts[0] || ""),
108+
host: cleanContextPart(parts[1] || ""),
109+
cwd: cleanContextPart(parts[2] || ""),
82110
};
83111
ws.send(JSON.stringify(context));
84112
return;
@@ -89,33 +117,37 @@ wss.on('connection', (ws) => {
89117
return;
90118
}
91119

92-
if (!text.includes('__AUTO_MARK_')) {
120+
if (
121+
!text.includes("__AUTO_MARK_") &&
122+
!/^echo\s+[^\n]+$/m.test(text.trim()) // evita imprimir líneas como echo AST_API
123+
) {
93124
ws.send(JSON.stringify({ output: text }));
94125
}
95126
});
96127

97-
stream.stderr.on('data', (data) => {
128+
stream.stderr.on("data", (data) => {
129+
console.error("❌ STDERR:", data.toString());
98130
ws.send(JSON.stringify({ error: data.toString() }));
99131
});
100132

101-
stream.on('close', () => {
102-
console.log('🔒 Shell cerrada');
133+
stream.on("close", () => {
134+
console.log("🔒 Shell cerrada");
103135
ssh.end();
104136
});
105137
});
106138
})
107-
.on('error', (err) => {
108-
console.error('❌ Error SSH:', err.message);
109-
ws.send(JSON.stringify({ error: 'Fallo SSH: ' + err.message }));
139+
.on("error", (err) => {
140+
console.error("❌ Error SSH:", err.message);
141+
ws.send(JSON.stringify({ error: "Fallo SSH: " + err.message }));
110142
})
111143
.connect({
112-
host: '192.168.20.58',
144+
host: "192.168.20.58",
113145
port: 22,
114-
username: 'chechojgb',
115-
password: '3209925728',
146+
username: "chechojgb",
147+
password: "3209925728",
116148
});
117149

118-
ws.on('message', (msg) => {
150+
ws.on("message", (msg) => {
119151
let command;
120152
try {
121153
const parsed = JSON.parse(msg);
@@ -126,48 +158,32 @@ wss.on('connection', (ws) => {
126158

127159
if (!shellStream || !command) return;
128160

129-
const isInternal = command.startsWith('__AUTO_COMPLETE__') || command.startsWith('__AUTO_SUGGEST__');
161+
const isInternal = command.startsWith("__AUTO_COMPLETE__") || command.startsWith("__AUTO_SUGGEST__");
130162

131163
if (isInternal) {
132-
if (command.startsWith('__AUTO_COMPLETE__')) {
133-
const prefix = command.replace('__AUTO_COMPLETE__', '').trim();
134-
const parts = prefix.split(/\s+/);
135-
const lastWord = parts[parts.length - 1] || '';
136-
const autoId = Date.now();
137-
138-
const baseCommand = parts[0];
139-
140-
let flags = '';
141-
if (baseCommand === 'cd') {
142-
flags = '-d';
143-
} else if (baseCommand === 'cat' || baseCommand === 'less' || baseCommand === 'nano') {
144-
flags = '-f';
145-
} else {
146-
flags = '-d -f';
147-
}
164+
const parts = command.replace(/__AUTO_(COMPLETE|SUGGEST)__/, "").trim().split(/\s+/);
165+
const lastWord = parts[parts.length - 1] || "";
166+
const baseCommand = parts[0];
167+
const autoId = Date.now();
148168

149-
const wrapped = `echo __AUTO_MARK_${autoId}__ && compgen ${flags} "${lastWord}" && echo __AUTO_MARK_${autoId}___END`;
150-
console.log(`🧠 Ejecutando autocompletado con flags ${flags} para: ${lastWord}`);
151-
shellStream.write(`${wrapped}\n`);
152-
return;
153-
}
154-
155-
if (command.startsWith('__AUTO_SUGGEST__')) {
156-
shellStream.write('ls -1\n');
157-
shellStream.once('data', (data) => {
158-
const lines = data.toString().split('\n').map((l) => l.trim()).filter(Boolean);
159-
ws.send(JSON.stringify({ suggestions: lines }));
160-
});
161-
return;
162-
}
169+
let flags = "-d -f";
170+
if (baseCommand === "cd") flags = "-d";
171+
if (["cat", "nano", "less"].includes(baseCommand)) flags = "-f";
172+
173+
const wrapped = `echo __AUTO_MARK_${autoId}__ && compgen ${flags} \"${lastWord}\" && echo __AUTO_MARK_${autoId}___END`;
174+
175+
console.log(`📤 Ejecutando ${command.startsWith("__AUTO_COMPLETE__") ? "autocompletado" : "sugerencias"} con:`, wrapped);
176+
177+
shellStream.write(`${wrapped}\n`);
178+
return;
163179
}
164180

165181
shellStream.write(`${command}\n`);
166-
shellStream.write('echo __CTX_START__ && whoami && echo __CTX__ && hostname && echo __CTX__ && pwd && echo __CTX_END__\n');
182+
shellStream.write("echo __CTX_START__ && whoami && echo __CTX__ && hostname && echo __CTX__ && pwd && echo __CTX_END__\n");
167183
});
168184

169-
ws.on('close', () => {
170-
console.log('🔌 Cliente desconectado');
185+
ws.on("close", () => {
186+
console.log("🔌 Cliente desconectado");
171187
if (shellStream) shellStream.end();
172188
ssh.end();
173189
});

resources/js/components/app-sidebar.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const mainNavItems = [
6565
{ title: 'Agregar', href: '/users/create' }
6666
],
6767
icon: SquareUserRound,
68+
requiredAreas: [2, 3],
6869

6970
},
7071
{
@@ -74,8 +75,18 @@ const mainNavItems = [
7475
{ title: 'Administrar', href: '/areas' },
7576
],
7677
icon: Tags,
77-
78+
requiredAreas: [2, 3],
79+
},
80+
{
81+
title: 'Terminales',
82+
href: '/terminales',
83+
children: [
84+
{ title: 'Administrar', href: '/test-terminal' },
85+
],
86+
icon: Tags,
87+
requiredAreas: [2, 3],
7888
},
89+
7990
];
8091

8192
const footerNavItems = [

0 commit comments

Comments
 (0)