Commit ecc99c81 authored by ajlarrosa's avatar ajlarrosa

Merge branch 'development'

parents c69bea51 2fca2f71
This diff is collapsed.
# Web Console - Guía de Instalación para Windows (Entorno Local)
Esta guía te ayudará a configurar y ejecutar el Web Console en un entorno local de Windows.
## Introducción
El Web Console es una aplicación web desarrollada en Go con frontend React que permite conectarse a servidores Linux mediante SSH directamente desde el navegador. Esta aplicación se integra con Cluster Panel para proporcionar una interfaz web para la gestión remota de servidores.
## Requisitos Previos
Antes de comenzar, asegúrate de tener instalado lo siguiente:
- **Go 1.13 o superior**: [Descargar Go](https://golang.org/dl/)
- Verificar instalación: `go version`
- **Node.js 18**: [Descargar Node.js](https://nodejs.org/)
- Verificar instalación: `node --version`
- **Yarn**: Se instala automáticamente con Node.js o puedes instalarlo con:
```bash
npm install -g yarn
```
- Verificar instalación: `yarn --version`
- **Statik**: Herramienta para embeber archivos estáticos en el binario Go
- Instalación: `go install github.com/rakyll/statik@latest`
- Nota: Asegúrate de que `%USERPROFILE%\go\bin` esté en tu PATH
## Instalación de Dependencias
### 1. Clonar o Navegar al Directorio del Proyecto
Si aún no tienes el código fuente, clónalo desde el repositorio o navega al directorio donde está ubicado:
```bash
cd C:\wamp64\www\web-console
```
### 2. Instalar Dependencias de Go
Las dependencias de Go se descargan automáticamente al compilar. Puedes verificar que están disponibles ejecutando:
```bash
go mod download
go mod verify
```
### 3. Instalar Dependencias del Frontend React
Navega al directorio `web` e instala las dependencias:
```bash
cd web
yarn install
cd ..
```
## Configuración
### 1. Configurar el Archivo de Configuración
El archivo de configuración principal se encuentra en `conf/config.yaml`. Este archivo controla la configuración del servidor.
Asegúrate de que el archivo `conf/config.yaml` existe y tiene una configuración similar a esta:
```yaml
site:
appname: ssh-web-console
listen_addr: :2222
runmode: prod
deploy_host: localhost
prod:
# http path of static files and views
static_prefix: /
dev: # config used in debug mode.
# https prefix of static files only
static_prefix: /static/
# redirect static files requests to this address
static_redirect: "localhost:8080"
static_dir: ./dist/
views_prefix: /
views_dir: views/
ssh:
buffer_checker_cycle_time: 60
jwt:
jwt_secret: secret.local.console
token_lifetime: 7200
issuer: issuer.local.ssh
query_token_key: _t
```
**Importante para desarrollo local:**
- `listen_addr: :2222` - Puerto en el que escuchará el servidor
- `runmode: prod` - Modo de ejecución (prod o dev)
- Ajusta el puerto si el 2222 ya está en uso
### 2. Configurar Variables de Entorno React (Producción)
Si estás compilando para producción, necesitas configurar las variables de entorno React antes de compilar el frontend.
Crea un archivo `.env` en el directorio `web/` con el siguiente contenido:
```env
REACT_APP_CLUSTER_URL=http://localhost:8000
REACT_APP_API_URL=localhost:2222
REACT_APP_ROUTER_BASE=
REACT_APP_API_HTTPS=false
```
**Nota:**
- `REACT_APP_CLUSTER_URL`: URL base de tu instalación de Cluster Panel (sin barra final)
- `REACT_APP_API_URL`: URL del servidor web console (opcional, por defecto usa window.location.host)
- Ajusta estas URLs según tu configuración local
Para desarrollo, estas variables son opcionales, pero se recomienda configurarlas si planeas usar la integración con Cluster Panel.
## Compilación Paso a Paso
### Método 1: Usando el Script build-and-run.bat (Recomendado)
El proyecto incluye un script batch que automatiza todo el proceso:
1. **Editar el Script (si es necesario):**
Abre `build-and-run.bat` y verifica las rutas:
- `ROOT_DIR`: Debe apuntar a tu directorio del proyecto
- `STATIK_PATH`: Debe apuntar a la ubicación de `statik.exe`
Ejemplo de configuración:
```batch
set "ROOT_DIR=C:\wamp64\www\web-console"
set "STATIK_PATH=C:\ProgramasGo\bin\statik.exe"
```
2. **Ejecutar el Script:**
Simplemente ejecuta:
```bash
build-and-run.bat
```
El script:
- Compilará el frontend React
- Ejecutará Statik para embeber los archivos estáticos
- Compilará el servidor Go
- Iniciará el servidor automáticamente
### Método 2: Compilación Manual
Si prefieres hacerlo paso a paso o el script no funciona, sigue estos pasos:
#### Paso 1: Compilar el Frontend React
```bash
cd web
yarn build
cd ..
```
Esto generará los archivos estáticos en `web/build/`.
#### Paso 2: Generar Archivos Statik
Statik embebe los archivos estáticos del frontend en el código Go:
```bash
statik --src=web/build
```
Esto generará archivos en el directorio `statik/`.
#### Paso 3: Compilar el Servidor Go
```bash
go build -o ssh-web-console.exe
```
Esto generará el ejecutable `ssh-web-console.exe` en el directorio actual.
## Ejecución
### Opción 1: Ejecución Directa
Si compilaste manualmente, ejecuta el binario:
```bash
ssh-web-console.exe
```
### Opción 2: Especificar Archivo de Configuración Personalizado
Si quieres usar un archivo de configuración en otra ubicación:
```bash
ssh-web-console.exe -config ruta/a/otro/config.yaml
```
### Opción 3: Ejecutar con build-and-run.bat
El script `build-and-run.bat` compila y ejecuta automáticamente.
## Verificación
1. **Verificar que el Servidor está Ejecutándose:**
El servidor debería mostrar un mensaje como:
```
listening on port :2222
```
2. **Acceder desde el Navegador:**
Abre tu navegador y visita:
```
http://localhost:2222
```
Deberías ver la interfaz de login del Web Console.
3. **Probar Conexión SSH:**
Ingresa:
- Host: IP o hostname del servidor al que quieres conectarte
- Puerto: 22 (por defecto)
- Usuario: Tu usuario SSH
- Contraseña: Tu contraseña SSH
## Integración con Cluster Panel
Para que el Web Console funcione integrado con Cluster Panel:
1. **Configurar la URL en Cluster Panel:**
En la configuración de Cluster Panel, asegúrate de que `urlWebConsole` esté configurada con la URL del Web Console:
```
http://localhost:2222
```
2. **Variable de Entorno REACT_APP_CLUSTER_URL:**
Al compilar el frontend, la variable `REACT_APP_CLUSTER_URL` debe apuntar a la URL de tu Cluster Panel:
```env
REACT_APP_CLUSTER_URL=http://localhost:8000
```
(Ajusta el puerto según tu configuración de Cluster Panel)
3. **Probar la Integración:**
Desde Cluster Panel, al acceder a la opción de Web Console, debería cargarse el Web Console en un iframe con las credenciales ya configuradas.
## Solución de Problemas Comunes
### Error: "statik: command not found"
**Solución:**
1. Instala Statik: `go install github.com/rakyll/statik@latest`
2. Asegúrate de que `%USERPROFILE%\go\bin` esté en tu PATH
3. Reinicia la terminal después de agregar al PATH
### Error: "yarn: command not found"
**Solución:**
1. Instala Node.js desde [nodejs.org](https://nodejs.org/)
2. Instala Yarn globalmente: `npm install -g yarn`
3. Verifica la instalación: `yarn --version`
### Error: "port 2222 already in use"
**Solución:**
1. Cambia el puerto en `conf/config.yaml`:
```yaml
listen_addr: :2223
```
2. O cierra el proceso que está usando el puerto 2222
### Error al compilar el frontend: "openssl-legacy-provider"
**Solución:**
El script de compilación ya incluye la opción `NODE_OPTIONS=--openssl-legacy-provider` en `package.json`. Si aún tienes problemas:
1. Actualiza Node.js a una versión más reciente
2. O ejecuta manualmente: `set NODE_OPTIONS=--openssl-legacy-provider && yarn build`
### El Web Console no se conecta con Cluster Panel
**Solución:**
1. Verifica que `REACT_APP_CLUSTER_URL` esté configurada correctamente en el archivo `.env` del directorio `web/`
2. Recompila el frontend después de cambiar las variables de entorno
3. Verifica que la URL de Cluster Panel en `urlWebConsole` coincida con la URL donde está ejecutándose
### Problemas de CORS o postMessage
**Solución:**
1. Asegúrate de que `REACT_APP_CLUSTER_URL` apunte exactamente a la URL de Cluster Panel (mismo protocolo, dominio y puerto)
2. Verifica que ambos servicios estén ejecutándose
3. Revisa la consola del navegador para ver errores específicos
## Referencias Adicionales
- **Repositorio del Proyecto**: [ssh-web-console](https://github.com/genshen/ssh-web-console)
- **Documentación de Go**: [golang.org/doc](https://golang.org/doc/)
- **Documentación de React**: [react.dev](https://react.dev/)
- **Documentación de Statik**: [github.com/rakyll/statik](https://github.com/rakyll/statik)
## Notas Adicionales
- Para desarrollo, puedes usar `runmode: dev` en `config.yaml` para habilitar características de desarrollo
- El servidor acepta conexiones desde cualquier IP. En producción, considera agregar restricciones de seguridad
- Las credenciales SSH no se almacenan en el servidor, se envían directamente a través de WebSocket
- El Web Console usa JWT para autenticación de sesiones SSH
## Comandos Rápidos de Referencia
```bash
# Compilar frontend
cd web && yarn build && cd ..
# Generar statik
statik --src=web/build
# Compilar servidor
go build -o ssh-web-console.exe
# Ejecutar servidor
ssh-web-console.exe
# Ejecutar todo con el script
build-and-run.bat
```
@echo off
setlocal
set "ROOT_DIR=C:\wamp64\www\web-console"
set "WEB_DIR=%ROOT_DIR%\web"
set "STATIK_PATH=C:\ProgramasGo\bin\statik.exe"
echo ========================================
echo Build y Ejecucion de Web-Console
echo ========================================
echo.
echo [1/4] Compilando frontend React...
pushd "%WEB_DIR%"
if %ERRORLEVEL% NEQ 0 (
echo ERROR: No se pudo cambiar al directorio web
pause
exit /b 1
)
call yarn build
if %ERRORLEVEL% NEQ 0 (
echo.
echo ERROR: Fallo la compilacion del frontend
popd
pause
exit /b 1
)
popd
echo.
echo OK: Frontend compilado exitosamente
echo.
echo [2/4] Ejecutando Statik...
pushd "%ROOT_DIR%"
if %ERRORLEVEL% NEQ 0 (
echo ERROR: No se pudo cambiar al directorio raiz
pause
exit /b 1
)
"%STATIK_PATH%" --src=web/build
if %ERRORLEVEL% NEQ 0 (
echo.
echo ERROR: Fallo la ejecucion de Statik
popd
pause
exit /b 1
)
echo.
echo OK: Statik ejecutado exitosamente
echo.
echo [3/4] Compilando servidor Go...
go build -o ssh-web-console.exe
if %ERRORLEVEL% NEQ 0 (
echo.
echo ERROR: Fallo la compilacion del servidor Go
popd
pause
exit /b 1
)
echo.
echo OK: Servidor Go compilado exitosamente
echo.
echo [4/4] Iniciando servidor web-console...
echo.
ssh-web-console.exe
if %ERRORLEVEL% NEQ 0 (
echo.
echo ERROR: El servidor se cerro con errores
popd
pause
exit /b 1
)
popd
pause
package controllers
import (
"net/http"
"strconv"
"github.com/genshen/ssh-web-console/src/models"
"github.com/genshen/ssh-web-console/src/utils"
"golang.org/x/crypto/ssh"
"net/http"
"strconv"
)
func SignIn(w http.ResponseWriter, r *http.Request) {
......@@ -35,13 +36,25 @@ func SignIn(w http.ResponseWriter, r *http.Request) {
session.Node = utils.NewSSHNode(userinfo.Host, userinfo.Port)
err := session.Connect(userinfo.Username, ssh.Password(userinfo.Password))
if err != nil {
errUnmarshal = models.JsonResponse{HasError: true, Message: models.SIGN_IN_FORM_TYPE_ERROR_PASSWORD}
// Incluir mensaje de error SSH detallado
errorDetail := err.Error()
errUnmarshal = models.JsonResponse{
HasError: true,
Message: models.SIGN_IN_FORM_TYPE_ERROR_PASSWORD,
ErrorDetail: errorDetail,
}
} else {
defer session.Close()
// create session
client, err := session.GetClient()
if err != nil {
// bad connection.
errUnmarshal = models.JsonResponse{
HasError: true,
Message: models.SIGN_IN_FORM_TYPE_ERROR_TEST,
ErrorDetail: "Error al obtener cliente SSH: " + err.Error(),
}
utils.ServeJSON(w, errUnmarshal)
return
}
if session, err := client.NewSession(); err == nil {
......@@ -51,10 +64,30 @@ func SignIn(w http.ResponseWriter, r *http.Request) {
utils.ServeJSON(w, errUnmarshal)
utils.SessionStorage.Put(token, expireUnix, userinfo)
return
} else {
// Error al crear token JWT
errUnmarshal = models.JsonResponse{
HasError: true,
Message: models.SIGN_IN_FORM_TYPE_ERROR_TEST,
ErrorDetail: "Error al crear token JWT: " + err.Error(),
}
}
} else {
// Error al ejecutar whoami
errUnmarshal = models.JsonResponse{
HasError: true,
Message: models.SIGN_IN_FORM_TYPE_ERROR_TEST,
ErrorDetail: "Error al ejecutar comando de prueba (whoami): " + err.Error(),
}
}
} else {
// Error al crear sesión SSH
errUnmarshal = models.JsonResponse{
HasError: true,
Message: models.SIGN_IN_FORM_TYPE_ERROR_TEST,
ErrorDetail: "Error al crear sesión SSH: " + err.Error(),
}
}
errUnmarshal = models.JsonResponse{HasError: true, Message: models.SIGN_IN_FORM_TYPE_ERROR_TEST}
}
} else {
errUnmarshal = models.JsonResponse{HasError: true, Message: models.SIGN_IN_FORM_TYPE_ERROR_VALID}
......
......@@ -15,7 +15,8 @@ type UserInfo struct {
}
type JsonResponse struct {
HasError bool `json:"has_error"`
Message interface{} `json:"message"`
Addition interface{} `json:"addition"`
HasError bool `json:"has_error"`
Message interface{} `json:"message"`
Addition interface{} `json:"addition"`
ErrorDetail string `json:"error_detail,omitempty"` // Mensaje de error detallado (ej: error SSH específico)
}
No preview for this file type
REACT_APP_CLUSTER_URL='__REACT_APP_CLUSTER_URL__'
\ No newline at end of file
REACT_APP_CLUSTER_URL='__REACT_APP_CLUSTER_URL__'
REACT_APP_API_URL='__REACT_APP_API_URL__'
REACT_APP_API_HTTPS=__REACT_APP_API_HTTPS__
\ No newline at end of file
This diff is collapsed.
const { override, addLessLoader } = require('customize-cra');
// Función que ajusta PostCSS Loader DESPUÉS de que addLessLoader lo configura
const fixPostCSSLoaderAfterLess = (config) => {
// Función recursiva para encontrar y ajustar PostCSS Loader en toda la configuración
const fixPostCSSInRules = (rules) => {
if (!rules || !Array.isArray(rules)) return;
rules.forEach((rule) => {
if (rule.oneOf) {
fixPostCSSInRules(rule.oneOf);
}
if (rule.use) {
const processUse = (use) => {
if (Array.isArray(use)) {
use.forEach((item) => {
if (typeof item === 'object' && item !== null && item.loader) {
if (item.loader.includes('postcss-loader') && item.options) {
// Si tiene plugins directamente (API antigua), moverlos a postcssOptions
if (item.options.plugins && !item.options.postcssOptions) {
item.options = {
postcssOptions: {
plugins: item.options.plugins
}
};
}
}
}
});
} else if (typeof use === 'object' && use !== null && use.loader) {
if (use.loader.includes('postcss-loader') && use.options) {
if (use.options.plugins && !use.options.postcssOptions) {
use.options = {
postcssOptions: {
plugins: use.options.plugins
}
};
}
}
}
};
processUse(rule.use);
}
});
};
if (config.module && config.module.rules) {
fixPostCSSInRules(config.module.rules);
}
return config;
};
module.exports = override(
addLessLoader({
lessOptions: {
javascriptEnabled: true,
}
})
}),
fixPostCSSLoaderAfterLess // Aplicar el fix DESPUÉS de addLessLoader
)
......@@ -11,23 +11,21 @@
"dependencies": {
"@rottitime/react-hook-message-event": "^1.0.8",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^12.1.10",
"@types/jest": "^26.0.15",
"@types/node": "^12.0.0",
"@types/react": "^16.9.53",
"@types/react-dom": "^16.9.8",
"@types/node": "^18.17.0",
"axios": "^0.21.1",
"evergreen-ui": "^6.4.0",
"file-saver": "^2.0.5",
"i18next": "^19.8.4",
"js-base64": "2.5.1",
"js-base64": "^3.0.0",
"react-dropzone": "^11.2.4",
"react-i18next": "^11.7.4",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.1",
"react-scripts": "^5.0.1",
"typescript": "^4.0.3",
"web-vitals": "^0.2.4",
"web-vitals": "^2.1.4",
"workbox-background-sync": "^5.1.3",
"workbox-broadcast-update": "^5.1.3",
"workbox-cacheable-response": "^5.1.3",
......@@ -46,7 +44,7 @@
},
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"build": "cross-env NODE_OPTIONS=--openssl-legacy-provider react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject",
"format": "prettier --config .prettierrc --write 'src/*'"
......@@ -81,11 +79,13 @@
"@babel/core": "^7.13.0",
"@types/file-saver": "^2.0.1",
"@types/js-base64": "^3.0.0",
"@types/react-router-dom": "^5.1.6",
"@typescript-eslint/eslint-plugin": "^4.6.1",
"@typescript-eslint/parser": "^4.6.1",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"babel-plugin-import": "^1.13.1",
"cross-env": "^7.0.3",
"customize-cra": "^1.0.0",
"react-app-rewired": "^2.2.1",
"eslint": "^7.32.0",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-prettier": "^3.1.4",
......@@ -95,8 +95,9 @@
"less-loader": "^7.1.0",
"lint-staged": "^10.5.1",
"prettier": "^2.2.1",
"react": "^17.0.2",
"react-app-rewired": "^2.1.6",
"react-dom": "^17.0.2"
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0"
}
}
......@@ -4,12 +4,18 @@ import Console from './components/Console';
import Home from './components/Home';
function App() {
// Type assertions needed for React 18 compatibility with react-router-dom v5 types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const SwitchComponent = Switch as any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const RouteComponent = Route as any;
return (
<div className="App">
<Switch>
<Route path="/console" exact component={Console} />
<Route path="/" component={Home} />
</Switch>
<SwitchComponent>
<RouteComponent path="/console" exact component={Console} />
<RouteComponent path="/" component={Home} />
</SwitchComponent>
</div>
);
}
......
......@@ -92,7 +92,7 @@ const Console = (props: RouteComponentProps) => {
const { t } = useTranslation(['translation', 'console']);
const [fitAddon] = useState<FitAddon>(new FitAddon());
const [webLinksAddon] = useState<WebLinksAddon>(new WebLinksAddon());
const [fullscreen, setFullscreen] = useState<boolean>(false);
const [fullscreen] = useState<boolean>(false);
const [connecting, setConnecting] = useState<ConnStatus>(
ConnStatus.Connecting,
);
......@@ -206,7 +206,7 @@ const Console = (props: RouteComponentProps) => {
background="rgba(27,33,47,0.86)">
<Heading padding={18} color="white">
{' '}
{t('title')}
{t('title') as string}
</Heading>
<Pane
padding={18}
......
......@@ -398,7 +398,6 @@ const FileTrans = ({
},
onUploadProgress: (percent: number) => {
setUploadStatus({ isUploading: true, hasError: false, percent: percent });
console.log(percent);
},
onUploadError: () => {
setUploadStatus({ isUploading: false, hasError: true, percent: 0 });
......
import React from 'react';
import { NavLink, Route, Switch } from 'react-router-dom';
import { Button, Pane, Heading } from 'evergreen-ui';
import { useTranslation } from 'react-i18next';
import { Route, Switch } from 'react-router-dom';
import Header from './layout/Header';
import Signin from './Signin';
import './home.less';
import headerLogo from '../assets/ssh.png';
const MainPage = () => {
const { t } = useTranslation(['home']);
return (
<>
<Pane
alignItems="center"
justifyContent="center"
display="flex"
flexDirection="column">
<div
style={{
minHeight: '360px',
marginTop: '10rem',
textAlign: 'center',
}}>
<img src={headerLogo} className="App-logo" alt="logo" />
<Heading marginBottom="0.6rem" marginTop="0.6rem" size={700}>
{t('home:welcome')}
</Heading>
<div>
<NavLink to="/signin" className="focus-ring-link">
<Button appearance="primary"> {t('home:goto_signin')} </Button>
</NavLink>
</div>
</div>
</Pane>
</>
);
};
const Home = () => {
// Type assertions needed for React 18 compatibility with react-router-dom v5 types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const SwitchComponent = Switch as any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const RouteComponent = Route as any;
return (
<div className="home-container">
<header className="home-content-header">
<Header />
</header>
<main className="home-content-main main-content-container">
<Switch>
<Route exact path={`/`} component={Signin} />
<Route path={`/signin`} component={Signin} />
</Switch>
<SwitchComponent>
<RouteComponent exact path={`/`} component={Signin} />
<RouteComponent path={`/signin`} component={Signin} />
</SwitchComponent>
</main>
</div>
);
......
......@@ -9,50 +9,102 @@ import apiRouters from '../config/api_routers';
const Signin = (props: RouteComponentProps) => {
React.useEffect(() => {
window.addEventListener('message', (event) => {
const baseUrl = process.env.REACT_APP_CLUSTER_URL as string;
console.log(event.origin);
console.log(process.env.REACT_APP_CLUSTER_URL);
console.log('-----');
console.log(!event.origin.includes(baseUrl));
console.log('-----');
console.log(event.data);
console.log('-----');
if (!event.origin.includes(process.env.REACT_APP_CLUSTER_URL!)) return;
doSignin(event.data);
});
const handleMessage = (event: MessageEvent) => {
if (!event.data || typeof event.data !== 'object') {
return;
}
const clusterUrl = process.env.REACT_APP_CLUSTER_URL;
if (!clusterUrl) {
return;
}
const normalizeOrigin = (url: string) => {
try {
if (url.includes('://')) {
const urlObj = new URL(url);
return urlObj.hostname.toLowerCase();
}
return url
.toLowerCase()
.replace(/^https?:\/\//, '')
.replace(/:\d+$/, '')
.split('/')[0]
.trim();
} catch {
return url
.toLowerCase()
.replace(/^https?:\/\//, '')
.replace(/:\d+$/, '')
.split('/')[0]
.trim();
}
};
const originHostname = normalizeOrigin(event.origin);
const clusterHostname = normalizeOrigin(clusterUrl);
if (originHostname !== clusterHostname) {
return;
}
if (event.data.host && event.data.username && event.data.password) {
doSignin(event.data);
}
};
window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
};
}, []);
const { t } = useTranslation(['signin']);
const doSignin = (data: Record<string, string>) => {
const normalizedData = {
host: String(data.host || '')
.toLowerCase()
.trim(),
port: String(data.port || '22').trim(),
username: String(data.username || '').trim(),
passwd: String(data.password || '').trim(),
};
if (
!normalizedData.host ||
!normalizedData.username ||
!normalizedData.passwd
) {
console.error('Signin.tsx - Missing required fields:', normalizedData);
toaster.danger(t('signin:form_has_error'));
return;
}
Utils.axiosInstance
.post(Utils.loadUrl(apiRouters.router.sign_in, null), {
host: data.host,
port: data.port,
username: data.username,
passwd: data.password,
host: normalizedData.host,
port: normalizedData.port,
username: normalizedData.username,
passwd: normalizedData.passwd,
})
.then((response) => {
console.log(response);
try {
if (!response.data || response.data.has_error) {
switch (response.data.message) {
case 0:
console.log(t('signin:form_has_error'));
toaster.danger(t('signin:form_has_error'));
break;
case 1:
console.log(t('signin:form_error_passport'));
toaster.danger(t('signin:form_error_passport'));
break;
case 2:
console.log(t('signin:form_error_ssh_login'));
toaster.danger(t('signin:form_error_ssh_login'));
break;
}
if (window && window.parent) {
console.log('Send Message has error');
window.parent.postMessage(
{
message: 'close',
......@@ -65,7 +117,6 @@ const Signin = (props: RouteComponentProps) => {
if (!response.data.addition) {
toaster.danger(t('signin:form_error_remote_server'));
if (window && window.parent) {
console.log('Send Message Else');
window.parent.postMessage(
{
message: 'close',
......@@ -76,8 +127,8 @@ const Signin = (props: RouteComponentProps) => {
props.history.push('/');
} else {
toaster.success(t('signin:signin_success'));
localStorage.setItem('user.host', data.host);
localStorage.setItem('user.username', data.username);
localStorage.setItem('user.host', normalizedData.host);
localStorage.setItem('user.username', normalizedData.username);
sessionStorage.setItem(
Config.jwt.tokenName,
response.data.addition,
......@@ -87,10 +138,11 @@ const Signin = (props: RouteComponentProps) => {
}
}
} catch (e) {
console.log(e.message);
toaster.danger(t('signin:form_error_ssh_login'));
const errorMessage = e instanceof Error ? e.message : String(e);
toaster.danger(
t('signin:form_error_ssh_login') + ': ' + errorMessage,
);
if (window && window.parent) {
console.log('Send Message Error 1');
window.parent.postMessage(
{
message: 'close',
......@@ -101,11 +153,10 @@ const Signin = (props: RouteComponentProps) => {
props.history.push('/');
}
})
.catch((e: Error) => {
console.log(e.message);
toaster.danger(t('signin:form_error_ssh_login') + ': ' + e.message);
.catch((e: unknown) => {
const errorMessage = e instanceof Error ? e.message : String(e);
toaster.danger(t('signin:form_error_ssh_login') + ': ' + errorMessage);
if (window && window.parent) {
console.log('Send Message Error 2');
window.parent.postMessage(
{
message: 'close',
......
......@@ -17,7 +17,7 @@ const Header = () => {
<Pane flex={1} alignItems="center" display="flex">
<Heading size={600}>
<Link to="/" className="focus-ring-link">
{t('title')}
{t('title') as string}
</Link>
</Heading>
</Pane>
......
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import './index.less';
import App from './App';
......@@ -9,13 +9,20 @@ import reportWebVitals from './reportWebVitals';
import config from './config/config';
import './locales/i18n';
ReactDOM.render(
const container = document.getElementById('root');
if (!container) {
throw new Error('Failed to find the root element');
}
const root = createRoot(container);
// Type assertion needed for React 18 compatibility with react-router-dom v5 types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const RouterComponent = BrowserRouter as any;
root.render(
<React.StrictMode>
<Router basename={config.router.basepath}>
<RouterComponent basename={config.router.basepath}>
<App />
</Router>
</RouterComponent>
</React.StrictMode>,
document.getElementById('root'),
);
// If you want your app to work offline and load faster, you can change
......
......@@ -8,12 +8,19 @@ interface TermSize {
const resize = {
bindTerminalResize: function (term: Terminal, websocket: WebSocket) {
const onTermResize = (size: TermSize) => {
websocket.send(
JSON.stringify({
type: 'resize',
data: { rows: size.rows, cols: size.cols },
}),
);
// Verificar que el WebSocket esté conectado antes de enviar datos
if (websocket.readyState === WebSocket.OPEN) {
try {
websocket.send(
JSON.stringify({
type: 'resize',
data: { rows: size.rows, cols: size.cols },
}),
);
} catch (error) {
console.error('Error sending resize message:', error);
}
}
};
// register resize event.
const resizeListener = term.onResize(onTermResize);
......
......@@ -50,14 +50,15 @@ registerRoute(
// Return true to signal that we want to use the handler.
return true;
},
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html'),
);
// An example runtime caching route for requests that aren't handled by the
// precache, in this case same-origin .png requests like those from in public/
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'),
({ url }) =>
url.origin === self.location.origin && url.pathname.endsWith('.png'),
// Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: 'images',
......@@ -66,7 +67,7 @@ registerRoute(
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 }),
],
})
}),
);
// This allows the web app to trigger skipWaiting via
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment