Skip to content

Commit 751b83c

Browse files
authored
Merge pull request #28 from chechojgb/terminal
add: edicion y seguridad de terminales
2 parents e4e7002 + 37ac5e2 commit 751b83c

File tree

5 files changed

+189
-4
lines changed

5 files changed

+189
-4
lines changed

app/Models/SshSession.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,6 @@ public function user(): BelongsTo
3333
{
3434
return $this->belongsTo(User::class);
3535
}
36-
protected $hidden = [ 'private_key'];
36+
protected $hidden = ['private_key'];
3737

3838
}

database/migrations/2025_06_29_144728_ssh_sessions.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public function up(): void {
2020
$table->text('private_key')->nullable(); // Opcional: autenticación por clave privada
2121
$table->text('description')->nullable(); // Opcional: para mostrar en el frontend
2222
$table->boolean('use_private_key')->default(false); // ¿Usar clave privada?
23-
$table->unsignedBigInteger('user_id')->nullable();
23+
$table->unsignedBigInteger('user_id');
2424
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
2525
$table->timestamps();
2626
});
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { useState, useEffect } from "react";
2+
import axios from "axios";
3+
4+
const TerminalModalEditContent = ({ onClose, setToast, terminal }) => {
5+
const [form, setForm] = useState({
6+
name: "",
7+
host: "",
8+
port: 22,
9+
username: "",
10+
password: "",
11+
use_private_key: false,
12+
private_key: "",
13+
description: "",
14+
});
15+
16+
const [errors, setErrors] = useState({});
17+
const [loading, setLoading] = useState(true);
18+
19+
useEffect(() => {
20+
if (!terminal) return;
21+
22+
axios.get(`/terminal/${terminal}/show`)
23+
.then((res) => {
24+
setForm({
25+
name: res.data.name || "",
26+
host: res.data.host || "",
27+
port: res.data.port || 22,
28+
username: res.data.username || "",
29+
password: res.data.password || "",
30+
use_private_key: !!res.data.use_private_key,
31+
private_key: res.data.private_key || "",
32+
description: res.data.description || "",
33+
});
34+
setLoading(false);
35+
})
36+
.catch((err) => {
37+
console.error("Error al cargar la terminal:", err);
38+
setToast({
39+
show: true,
40+
success: false,
41+
message: "Error al cargar el terminal.",
42+
});
43+
onClose();
44+
});
45+
}, [terminal]);
46+
47+
const handleChange = (e) => {
48+
const { name, value, type, checked } = e.target;
49+
setForm((prev) => ({
50+
...prev,
51+
[name]: type === "checkbox" ? checked : value,
52+
}));
53+
};
54+
55+
const handleSubmit = async (e) => {
56+
e.preventDefault();
57+
setErrors({});
58+
setToast({ show: false, success: false, message: "" });
59+
60+
try {
61+
const res = await axios.put(`/terminal/${terminal}/edit`, form);
62+
setToast({
63+
show: true,
64+
success: true,
65+
message: res.data?.message || "Terminal actualizada correctamente.",
66+
});
67+
onClose();
68+
} catch (err) {
69+
if (err.response?.status === 422) {
70+
setErrors(err.response.data.errors);
71+
setToast({
72+
show: true,
73+
success: false,
74+
message: "Error al actualizar la Terminal.",
75+
});
76+
}
77+
}
78+
79+
setTimeout(() => setToast((prev) => ({ ...prev, show: false })), 3000);
80+
};
81+
82+
if (loading) {
83+
return <p className="p-4">Cargando datos de la terminal...</p>;
84+
}
85+
86+
return (
87+
<>
88+
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
89+
Editar Terminal
90+
</h3>
91+
<form onSubmit={handleSubmit} className="space-y-4">
92+
{[
93+
{ label: "Nombre", name: "name", type: "text" },
94+
{ label: "Host", name: "host", type: "text" },
95+
{ label: "Puerto", name: "port", type: "number" },
96+
{ label: "Usuario", name: "username", type: "text" },
97+
{ label: "Contraseña", name: "password", type: "password" },
98+
].map(({ label, name, type }) => (
99+
<div key={name}>
100+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
101+
{label}
102+
</label>
103+
<input
104+
type={type}
105+
name={name}
106+
value={form[name]}
107+
onChange={handleChange}
108+
className="w-full mt-1 rounded-md p-2 border border-gray-300 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
109+
/>
110+
{errors[name] && (
111+
<p className="text-sm text-red-600 mt-1">{errors[name][0]}</p>
112+
)}
113+
</div>
114+
))}
115+
116+
<div>
117+
<label className="inline-flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300">
118+
<input
119+
type="checkbox"
120+
name="use_private_key"
121+
checked={form.use_private_key}
122+
onChange={handleChange}
123+
/>
124+
Usar clave privada
125+
</label>
126+
</div>
127+
128+
{form.use_private_key && (
129+
<div>
130+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
131+
Clave privada (PEM)
132+
</label>
133+
<textarea
134+
name="private_key"
135+
value={form.private_key}
136+
onChange={handleChange}
137+
rows={4}
138+
className="w-full mt-1 rounded-md p-2 border border-gray-300 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
139+
placeholder="-----BEGIN RSA PRIVATE KEY----- ..."
140+
/>
141+
{errors.private_key && (
142+
<p className="text-sm text-red-600 mt-1">{errors.private_key[0]}</p>
143+
)}
144+
</div>
145+
)}
146+
147+
<div>
148+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
149+
Descripción
150+
</label>
151+
<textarea
152+
name="description"
153+
value={form.description}
154+
onChange={handleChange}
155+
rows={2}
156+
className="w-full mt-1 rounded-md p-2 border border-gray-300 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
157+
/>
158+
{errors.description && (
159+
<p className="text-sm text-red-600 mt-1">{errors.description[0]}</p>
160+
)}
161+
</div>
162+
163+
<div className="flex justify-end gap-2 pt-2">
164+
<button
165+
type="button"
166+
onClick={onClose}
167+
className="bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 text-gray-700 dark:text-gray-200 px-4 py-2 rounded-md text-sm"
168+
>
169+
Cancelar
170+
</button>
171+
<button
172+
type="submit"
173+
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium"
174+
>
175+
Guardar cambios
176+
</button>
177+
</div>
178+
</form>
179+
</>
180+
);
181+
};
182+
183+
export default TerminalModalEditContent;

resources/js/pages/terminalAdmin.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { HiOutlineArrowRight, HiShoppingCart } from "react-icons/hi";
1010
import { ClipboardList } from 'lucide-react';
1111
import AgentModalWrapper from '@/components/agentsModalWrapper';
1212
import AreaModalContent from '@/components/areaModalContent';
13-
import AreaModalEditContent from '@/components/areaModalEditContent';
13+
import TerminalModalEditContent from '@/components/terminalModalEdit';
1414
import { themeByProject } from '@/components/utils/theme';
1515
import { HiCheck, HiX } from "react-icons/hi";
1616
import { Toast } from "flowbite-react";
@@ -127,7 +127,7 @@ export default function Areas() {
127127

128128
{modalOpenEdit && (
129129
<AgentModalWrapper closeModal={closeModal}>
130-
<AreaModalEditContent onClose={closeModal} setToast={setToast} area={selectedArea}/>
130+
<TerminalModalEditContent onClose={closeModal} setToast={setToast} terminal={selectedArea}/>
131131
</AgentModalWrapper>
132132
)}
133133
</AppLayout>

routes/web.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@
115115

116116
Route::get('terminal/index', [SshSessionController::class, 'index'])->name('terminal_index');
117117
Route::post('terminal.store', [SshSessionController::class, 'store'])->name('terminal.store');
118+
Route::get('/terminal/{sshSession}/show', [SshSessionController::class, 'show']);
119+
Route::put('/terminal/{sshSession}/edit', [SshSessionController::class, 'update']);
118120

119121

120122
Route::get('/terminales/{id}', function ($id) {

0 commit comments

Comments
 (0)