mirror of
https://github.com/MCSManager/MCSManager.git
synced 2024-11-21 03:12:10 +08:00
Merge branch 'master' of github.com:MCSManager/MCSManager
This commit is contained in:
commit
1dcafd9128
1
.dockerignore
Symbolic link
1
.dockerignore
Symbolic link
@ -0,0 +1 @@
|
||||
.gitignore
|
BIN
.github/panel-custom-layout.gif
vendored
Normal file
BIN
.github/panel-custom-layout.gif
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 MiB |
102
.github/workflows/docker.yml
vendored
Normal file
102
.github/workflows/docker.yml
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
name: Release Docker Build
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build-web:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Docker meta web
|
||||
id: meta_web
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
name=ghcr.io/${{ github.repository }}-web
|
||||
name=githubyumao/mcsmanager-web,enable=${{ github.repository == 'MCSManager/MCSManager' }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: ${{ github.repository == 'MCSManager/MCSManager' }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
|
||||
- name: Build and Push Web
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: dockerfile/web.dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.meta_web.outputs.tags }}
|
||||
labels: ${{ steps.meta_web.outputs.labels }}
|
||||
build-args: |
|
||||
BUILDPLATFORM=linux/amd64
|
||||
|
||||
build-daemon:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
java_version: [8, 11, 17, 21]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Docker meta daemon
|
||||
id: meta_daemon
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
name=ghcr.io/${{ github.repository }}-daemon,enable=${{ matrix.java_version == 21 }}
|
||||
name=githubyumao/mcsmanager-daemon,enable=${{ github.repository == 'MCSManager/MCSManager' && matrix.java_version == 21 }}
|
||||
name=ghcr.io/${{ github.repository }}-daemon-jdk${{ matrix.java_version }}
|
||||
name=githubyumao/mcsmanager-daemon-jdk${{ matrix.java_version }},enable=${{ github.repository == 'MCSManager/MCSManager' }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: ${{ github.repository == 'MCSManager/MCSManager' }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
|
||||
- name: Build and Push Daemon
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: dockerfile/daemon.dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.meta_daemon.outputs.tags }}
|
||||
labels: ${{ steps.meta_daemon.outputs.labels }}
|
||||
build-args: |
|
||||
BUILDPLATFORM=linux/amd64
|
||||
JAVA_RUNTIME=${{ matrix.java_version }}
|
57
.github/workflows/release.yml
vendored
Normal file
57
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
name: Release Build
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
chmod a+x ./install-dependents.sh
|
||||
chmod a+x ./build.sh
|
||||
./install-dependents.sh
|
||||
./build.sh
|
||||
|
||||
- name: Add binaries to production files
|
||||
run: wget --input-file=lib-urls.txt --directory-prefix=production-code/daemon/lib/
|
||||
|
||||
- name: Create linux and windows build
|
||||
run: |
|
||||
cp -r production-code dist_linux
|
||||
mv production-code dist_windows
|
||||
|
||||
- name: Copy startup scripts
|
||||
run: |
|
||||
cp prod-scripts/linux/* dist_linux/
|
||||
cp prod-scripts/windows/* dist_windows/
|
||||
|
||||
- name: Copy node runtime to windows build
|
||||
run: |
|
||||
wget https://nodejs.org/download/release/latest-v20.x/win-x64/node.exe -O dist_windows/daemon/node_app.exe
|
||||
cp dist_windows/daemon/node_app.exe dist_windows/web/node_app.exe
|
||||
|
||||
- name: Create archive
|
||||
run: |
|
||||
mv dist_linux/ mcsmanager/
|
||||
tar czf mcsmanager_linux_release.tar.gz mcsmanager/
|
||||
rm -rf mcsmanager/
|
||||
mv dist_windows/ mcsmanager/
|
||||
zip -r mcsmanager_windows_release.zip mcsmanager/
|
||||
|
||||
- name: Upload assets to release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
mcsmanager_windows_release.zip
|
||||
mcsmanager_linux_release.tar.gz
|
15
README.md
15
README.md
@ -16,8 +16,8 @@
|
||||
|
||||
[Official Website](http://mcsmanager.com/) | [Docs](https://docs.mcsmanager.com/) | [Discord](https://discord.gg/BNpYMVX7Cd)
|
||||
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) |
|
||||
[日本語](README_JP.md)
|
||||
[简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) |
|
||||
[日本語](README_JP.md) | [Spanish](README_ES.md)
|
||||
</div>
|
||||
|
||||
<br />
|
||||
@ -32,10 +32,21 @@ MCSManager has already gained a certain level of popularity within the community
|
||||
|
||||
MCSManager **supports English, French, German, Italian, Japanese, Portuguese, Simplified Chinese, and Traditional Chinese**, with plans to add support for more languages in the future!
|
||||
|
||||
|
||||
**Terminal**
|
||||
|
||||
![failed_to_load_screenshot.png](/.github/panel-image.png)
|
||||
|
||||
**Instance List**
|
||||
|
||||
![failed_to_load_screenshot.png](/.github/panel-instances.png)
|
||||
|
||||
**Custom Layout**
|
||||
|
||||
![failed_to_load_screenshot.png](/.github/panel-custom-layout.gif)
|
||||
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
1. One-click deployment of `Minecraft` Java/Bedrock Server
|
||||
|
199
README_ES.md
Normal file
199
README_ES.md
Normal file
@ -0,0 +1,199 @@
|
||||
<div align="center">
|
||||
<a href="https://mcsmanager.com/" target="_blank">
|
||||
<img src="https://public-link.oss-cn-shenzhen.aliyuncs.com/mcsm_picture/logo.png" alt="MCSManagerLogo.png" width="510px" />
|
||||
</a>
|
||||
|
||||
<br />
|
||||
|
||||
<h1 id="mcsmanager">
|
||||
<a href="https://mcsmanager.com/" target="_blank">Panel de MCSManager</a>
|
||||
</h1>
|
||||
|
||||
[![--](https://img.shields.io/badge/Support-Windows/Linux-green.svg)](https://github.com/MCSManager)
|
||||
[![Estado](https://img.shields.io/badge/npm-v8.9.14-blue.svg)](https://www.npmjs.com/)
|
||||
[![Estado](https://img.shields.io/badge/node-v16.20.2-blue.svg)](https://nodejs.org/en/download/)
|
||||
[![Licencia](https://img.shields.io/badge/License-Apache%202.0-red.svg)](https://github.com/MCSManager)
|
||||
|
||||
[Sitio Oficial](http://mcsmanager.com/) | [Documentación](https://docs.mcsmanager.com/) | [Discord](https://discord.gg/BNpYMVX7Cd)
|
||||
|
||||
[Inglés](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) | [日本語](README_JP.md)
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
## ¿Qué es MCSManager?
|
||||
|
||||
**Panel de MCSManager** (MCSM) es un **panel de control moderno, seguro y distribuido** diseñado para gestionar servidores de juego de Minecraft y Steam.
|
||||
|
||||
MCSManager ha ganado popularidad en la comunidad, especialmente en Minecraft. MCSManager ofrece una solución centralizada para gestionar múltiples instancias de servidor y proporciona un sistema de permisos multiusuario seguro y confiable. Nos comprometemos a apoyar a los administradores de servidores no solo para Minecraft, sino también para Terraria y varios juegos de Steam. Nuestro objetivo es fomentar una comunidad próspera y de apoyo en la gestión de servidores de juego.
|
||||
|
||||
MCSManager **admite inglés, francés, alemán, italiano, japonés, portugués, chino simplificado y chino tradicional**, ¡y planea agregar más idiomas en el futuro!
|
||||
|
||||
![failed_to_load_screenshot.png](/.github/panel-image.png)
|
||||
|
||||
![failed_to_load_screenshot.png](/.github/panel-instances.png)
|
||||
|
||||
## Características
|
||||
|
||||
1. Implementación con un clic de servidor `Minecraft` Java/Bedrock.
|
||||
2. Compatible con la mayoría de servidores de juegos de `Steam` (p. ej., `Palworld`, `Squad`, `Project Zomboid`, `Terraria`, etc.).
|
||||
3. Interfaz personalizable; crea tu propio diseño.
|
||||
4. Soporte para virtualización con `Docker`, multiusuario y servicios comerciales.
|
||||
5. Gestiona múltiples servidores desde una sola interfaz web.
|
||||
6. ¡Y más!
|
||||
|
||||
<br />
|
||||
|
||||
## Entorno de Ejecución
|
||||
|
||||
MCSM es compatible con `Windows` y `Linux`. El único requisito es `Node.js` y algunas librerías **para descompresión**.
|
||||
|
||||
Requiere [Node.js 16.20.2](https://nodejs.org/en) o superior.
|
||||
|
||||
<br />
|
||||
|
||||
## Instalación
|
||||
|
||||
### Windows
|
||||
|
||||
Para Windows, ofrecemos archivos ejecutables empaquetados:
|
||||
|
||||
Ir a: [https://mcsmanager.com/](https://mcsmanager.com/)
|
||||
|
||||
<br />
|
||||
|
||||
### Linux
|
||||
|
||||
**Despliegue con un solo comando**
|
||||
|
||||
> El script necesita registrar servicios del sistema, requiere permisos de root.
|
||||
|
||||
```bash
|
||||
sudo su -c "wget -qO- https://script.mcsmanager.com/setup.sh | bash"
|
||||
```
|
||||
|
||||
**Uso**
|
||||
|
||||
```bash
|
||||
systemctl start mcsm-{web,daemon}
|
||||
systemctl stop mcsm-{web,daemon}
|
||||
```
|
||||
|
||||
- Solo compatible con Ubuntu/Centos/Debian/Archlinux.
|
||||
- Directorio de instalación: `/opt/mcsmanager/`.
|
||||
|
||||
<br />
|
||||
|
||||
**Instalación Manual en Linux**
|
||||
|
||||
- Si el script de instalación falla, puedes intentar instalarlo manualmente.
|
||||
|
||||
```bash
|
||||
# Crear directorio /opt si no existe
|
||||
mkdir /opt
|
||||
# Cambiar a /opt
|
||||
cd /opt/
|
||||
# Descargar Node.js 20.11. Si ya tienes Node.js 16+ instalado, omite este paso.
|
||||
wget https://nodejs.org/dist/v20.11.0/node-v20.11.0-linux-x64.tar.xz
|
||||
# Descomprimir Node.js
|
||||
tar -xvf node-v20.11.0-linux-x64.tar.xz
|
||||
# Agregar Node.js al PATH del sistema
|
||||
ln -s /opt/node-v20.11.0-linux-x64/bin/node /usr/bin/node
|
||||
ln -s /opt/node-v20.11.0-linux-x64/bin/npm /usr/bin/npm
|
||||
|
||||
# Preparar el directorio de instalación de MCSM
|
||||
mkdir /opt/mcsmanager/
|
||||
cd /opt/mcsmanager/
|
||||
|
||||
# Descargar MCSManager
|
||||
wget https://github.com/MCSManager/MCSManager/releases/latest/download/mcsmanager_linux_release.tar.gz
|
||||
tar -zxf mcsmanager_linux_release.tar.gz
|
||||
|
||||
# Instalar dependencias
|
||||
./install.sh
|
||||
|
||||
# Abrir dos terminales o pantallas.
|
||||
|
||||
# Iniciar el daemon primero.
|
||||
./start-daemon.sh
|
||||
|
||||
# Iniciar la interfaz web en la segunda terminal o pantalla.
|
||||
./start-web.sh
|
||||
|
||||
# Para acceder a la web, ir a http://localhost:23333/
|
||||
# En general, la interfaz web escaneará y añadirá automáticamente el daemon local.
|
||||
```
|
||||
|
||||
Este método de instalación no configura automáticamente MCSManager como un servicio del sistema. Por lo tanto, es necesario usar `screen` para la administración. Para quienes quieran administrar MCSManager a través de un servicio del sistema, por favor consulta nuestra wiki/documentación.
|
||||
|
||||
<br />
|
||||
|
||||
## Compatibilidad del Navegador
|
||||
|
||||
- Compatible con navegadores modernos como `Chrome`, `Firefox` y `Safari`.
|
||||
- El soporte para `IE` ha sido discontinuado.
|
||||
|
||||
<br />
|
||||
|
||||
## Desarrollo
|
||||
|
||||
Esta sección está dirigida específicamente a desarrolladores. Los usuarios generales pueden ignorarla sin problema.
|
||||
|
||||
### MacOS
|
||||
|
||||
```bash
|
||||
git clone https://github.com/MCSManager/MCSManager.git
|
||||
./install-dependents.sh
|
||||
./npm-dev-macos.sh
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
```bash
|
||||
git clone https://github.com/MCSManager/MCSManager.git
|
||||
./install-dependents.bat
|
||||
./npm-dev-windows.bat
|
||||
```
|
||||
|
||||
### Construir Versión de Producción
|
||||
|
||||
```bash
|
||||
./build.bat # Windows
|
||||
./build.sh # MacOS
|
||||
```
|
||||
|
||||
Luego, deberás ir a los proyectos [PTY](https://github.com/MCSManager/PTY) y [Zip-Tools](https://github.com/MCSManager/Zip-Tools) para descargar los archivos binarios correspondientes y colocarlos en el directorio `daemon/lib` para asegurar el funcionamiento adecuado del `Terminal de Emulación` y la `Descompresión de Archivos`.
|
||||
|
||||
<br />
|
||||
|
||||
## Contribución de Código
|
||||
|
||||
Si experimentas problemas al usar MCSManager, puedes [enviar un Issue](https://github.com/MCSManager/MCSManager/issues/new/choose). Alternativamente, puedes hacer un fork del proyecto y contribuir directamente enviando un Pull Request.
|
||||
|
||||
Asegúrate de que el código enviado siga nuestro estilo de codificación existente. Para más detalles, consulta las pautas en [este issue](https://github.com/MCSManager/MCSManager/issues/544).
|
||||
|
||||
<br />
|
||||
|
||||
## Reporte de Errores
|
||||
|
||||
**Abrir Issue:** [Haz clic aquí](https://github.com/MCSManager/MCSManager/issues/new/choose)
|
||||
|
||||
**Reporte de Vulnerabilidades de Seguridad:** [SECURITY.md](SECURITY.md) (en ingles)
|
||||
|
||||
<br />
|
||||
|
||||
## Internacionalización
|
||||
|
||||
Gracias a estos colaboradores por proporcionar una gran cantidad de traducciones:
|
||||
|
||||
- [KevinLu2000](https://github.com/KevinLu2000)
|
||||
- [Unitwk](https://github.com/unitwk)
|
||||
- [JianyueLab](https://github.com/JianyueLab)
|
||||
- [IceBrick](https://github.com/IceBrick01)
|
||||
|
||||
<br />
|
||||
|
||||
## Licencia
|
||||
El código fuente de MCSManager está licenciado bajo la [Licencia Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
||||
Copyright ©2024 MCSManager.
|
@ -17,7 +17,7 @@
|
||||
|
||||
[HP](http://mcsmanager.com/) | [ドキュメント](https://docs.mcsmanager.com/) | [Discord](https://discord.gg/BNpYMVX7Cd)
|
||||
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md)
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) | [Spanish](README_ES.md)
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
[Website Oficial](http://mcsmanager.com/) | [Documentação](https://docs.mcsmanager.com/) | [Discord](https://discord.gg/BNpYMVX7Cd)
|
||||
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) |
|
||||
[日本語](README_JP.md)
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) |
|
||||
[日本語](README_JP.md) | [Spanish](README_ES.md)
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
[官方網站](http://mcsmanager.com/) | [教學說明](https://docs.mcsmanager.com/#/zh-cn/) | [TG 群組](https://t.me/MCSManager_dev) | [成為贊助者](https://afdian.net/a/mcsmanager)
|
||||
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) |
|
||||
[日本語](README_JP.md)
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [Português BR](README_PTBR.md) |
|
||||
[日本語](README_JP.md) | [Spanish](README_ES.md)
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
[官方网站](http://mcsmanager.com/) | [使用文档](https://docs.mcsmanager.com/#/zh-cn/) | [QQ 群](https://jq.qq.com/?_wv=1027&k=Pgl9ScGw) | [TG 群](https://t.me/MCSManager_dev) | [成为赞助者](https://afdian.net/a/mcsmanager)
|
||||
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) |
|
||||
[日本語](README_JP.md)
|
||||
[English](README.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) |
|
||||
[日本語](README_JP.md) | [Spanish](README_ES.md)
|
||||
|
||||
</div>
|
||||
|
||||
|
41
build.sh
41
build.sh
@ -2,6 +2,8 @@
|
||||
|
||||
set -e
|
||||
|
||||
BASE_PATH=$(pwd)
|
||||
|
||||
npm run preview-build
|
||||
|
||||
rm -rf production-code
|
||||
@ -9,42 +11,45 @@ rm -rf ./daemon/dist ./daemon/production
|
||||
rm -rf ./panel/dist ./panel/production
|
||||
|
||||
echo "Build daemon..."
|
||||
cd daemon
|
||||
cd "${BASE_PATH}/daemon"
|
||||
npm run build
|
||||
|
||||
echo "Build panel..."
|
||||
cd ..
|
||||
cd panel
|
||||
cd "${BASE_PATH}/panel"
|
||||
npm run build
|
||||
|
||||
echo "Build frontend..."
|
||||
cd ..
|
||||
cd frontend
|
||||
cd "${BASE_PATH}/frontend"
|
||||
npm run build
|
||||
|
||||
echo "Collecting files..."
|
||||
cd ..
|
||||
cd "${BASE_PATH}"
|
||||
|
||||
mkdir production-code
|
||||
mkdir production-code/daemon
|
||||
mkdir production-code/web
|
||||
mkdir production-code/web/public
|
||||
|
||||
mv ./daemon/production/app.js ./production-code/daemon
|
||||
mv ./daemon/production/app.js.map ./production-code/daemon
|
||||
cp -f ./daemon/package.json ./production-code/daemon/package.json
|
||||
cp -f ./daemon/package-lock.json ./production-code/daemon/package-lock.json
|
||||
mv "${BASE_PATH}/daemon/production/app.js" "${BASE_PATH}/production-code/daemon"
|
||||
mv "${BASE_PATH}/daemon/production/app.js.map" "${BASE_PATH}/production-code/daemon"
|
||||
cp -f "${BASE_PATH}/daemon/package.json" "${BASE_PATH}/production-code/daemon/package.json"
|
||||
cp -f "${BASE_PATH}/daemon/package-lock.json" "${BASE_PATH}/production-code/daemon/package-lock.json"
|
||||
|
||||
mv ./panel/production/app.js ./production-code/web
|
||||
mv ./panel/production/app.js.map ./production-code/web
|
||||
cp -f ./panel/package.json ./production-code/web/package.json
|
||||
cp -f ./panel/package-lock.json ./production-code/web/package-lock.json
|
||||
mv "${BASE_PATH}/panel/production/app.js" "${BASE_PATH}/production-code/web"
|
||||
mv "${BASE_PATH}/panel/production/app.js.map" "${BASE_PATH}/production-code/web"
|
||||
cp -f "${BASE_PATH}/panel/package.json" "${BASE_PATH}/production-code/web/package.json"
|
||||
cp -f "${BASE_PATH}/panel/package-lock.json" "${BASE_PATH}/production-code/web/package-lock.json"
|
||||
|
||||
mv ./frontend/dist/* ./production-code/web/public
|
||||
mv "${BASE_PATH}"/frontend/dist/* "${BASE_PATH}/production-code/web/public"
|
||||
|
||||
rm -rf ./daemon/dist ./daemon/production
|
||||
rm -rf ./panel/dist ./panel/production
|
||||
rm -rf ./frontend/dist
|
||||
rm -rf "${BASE_PATH}/daemon/dist" "${BASE_PATH}/daemon/production"
|
||||
rm -rf "${BASE_PATH}/panel/dist" "${BASE_PATH}/panel/production"
|
||||
rm -rf "${BASE_PATH}/frontend/dist"
|
||||
|
||||
cd "${BASE_PATH}/production-code/daemon"
|
||||
npm install --production --no-fund --no-audit
|
||||
cd "${BASE_PATH}/production-code/web"
|
||||
npm install --production --no-fund --no-audit
|
||||
|
||||
echo "------------"
|
||||
echo "Compilation completed!"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mcsmanager-daemon",
|
||||
"version": "4.5.0",
|
||||
"version": "4.5.2",
|
||||
"description": "Provides remote control capability for MCSManager to manage processes, scheduled tasks, I/O streams, and more",
|
||||
"scripts": {
|
||||
"dev": "ts-node --project tsconfig.json src/app.ts",
|
||||
|
@ -1,12 +0,0 @@
|
||||
import Instance from "../instance/instance";
|
||||
import InstanceCommand from "./base/command";
|
||||
|
||||
export default class SendCommand extends InstanceCommand {
|
||||
constructor(public readonly cmd: string) {
|
||||
super("SendCommand");
|
||||
}
|
||||
|
||||
async exec(instance: Instance) {
|
||||
return await instance.execPreset("command", this.cmd);
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
import Docker from "dockerode";
|
||||
import Instance from "../../instance/instance";
|
||||
import InstanceCommand from "../base/command";
|
||||
import { t } from "i18next";
|
||||
import { DefaultDocker } from "../../../service/docker_service"
|
||||
import { DefaultDocker } from "../../../service/docker_service";
|
||||
|
||||
export async function checkImage(name: string) {
|
||||
const docker = new DefaultDocker();
|
||||
|
@ -3,7 +3,6 @@ import Instance from "../../instance/instance";
|
||||
import InstanceCommand from "../base/command";
|
||||
import logger from "../../../service/log";
|
||||
import fs from "fs-extra";
|
||||
import { t } from "i18next";
|
||||
import DockerPullCommand from "./docker_pull";
|
||||
import {
|
||||
DockerProcessAdapter,
|
||||
@ -17,8 +16,8 @@ export default class DockerStartCommand extends InstanceCommand {
|
||||
}
|
||||
|
||||
async exec(instance: Instance, source = "Unknown") {
|
||||
if (!instance.config.cwd || !instance.config.ie || !instance.config.oe)
|
||||
throw new StartupDockerProcessError($t("TXT_CODE_instance.dirEmpty"));
|
||||
if (!instance.hasCwdPath() || !instance.config.ie || !instance.config.oe)
|
||||
throw new StartupDockerProcessError($t("TXT_CODE_a6424dcc"));
|
||||
if (!fs.existsSync(instance.absoluteCwdPath()))
|
||||
throw new StartupDockerProcessError($t("TXT_CODE_instance.dirNoE"));
|
||||
|
||||
@ -30,16 +29,13 @@ export default class DockerStartCommand extends InstanceCommand {
|
||||
}
|
||||
|
||||
// Docker docks to the process adapter
|
||||
const isTty = instance.config.terminalOption.pty;
|
||||
const workingDir = instance.config.docker.workingDir ?? "/workspace/";
|
||||
const processAdapter = new DockerProcessAdapter(new SetupDockerContainer(instance));
|
||||
await processAdapter.start({
|
||||
isTty,
|
||||
isTty: instance.config.terminalOption.pty,
|
||||
w: instance.config.terminalOption.ptyWindowCol,
|
||||
h: instance.config.terminalOption.ptyWindowCol
|
||||
});
|
||||
|
||||
instance.println("CONTAINER", t("TXT_CODE_e76e49e9") + workingDir);
|
||||
instance.started(processAdapter);
|
||||
logger.info(
|
||||
$t("TXT_CODE_instance.successful", {
|
||||
|
@ -4,12 +4,27 @@ import Instance from "../../instance/instance";
|
||||
import { encode } from "iconv-lite";
|
||||
import InstanceCommand from "../base/command";
|
||||
|
||||
export const CTRL_C = "\x03";
|
||||
|
||||
export function isExitCommand(instance: Instance, buf: any) {
|
||||
if (String(buf).toLowerCase() === "^c") {
|
||||
instance.process?.kill("SIGINT");
|
||||
return true;
|
||||
}
|
||||
if (buf == CTRL_C) {
|
||||
instance.process?.write(CTRL_C);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export default class GeneralSendCommand extends InstanceCommand {
|
||||
constructor() {
|
||||
super("SendCommand");
|
||||
}
|
||||
|
||||
async exec(instance: Instance, buf?: any): Promise<any> {
|
||||
if (isExitCommand(instance, buf)) return;
|
||||
// The server shutdown command needs to send a command, but before the server shutdown command is executed, the status will be set to the shutdown state.
|
||||
// So here the command can only be executed by whether the process exists or not
|
||||
if (instance?.process) {
|
||||
|
@ -28,9 +28,9 @@ export default class GeneralInstallCommand extends InstanceCommand {
|
||||
instance.setLock(true);
|
||||
instance.status(Instance.STATUS_BUSY);
|
||||
instance.println($t("TXT_CODE_1704ea49"), $t("TXT_CODE_cbc235ad"));
|
||||
if (instance.config.cwd.length > 1) {
|
||||
fs.removeSync(instance.config.cwd);
|
||||
fs.mkdirsSync(instance.config.cwd);
|
||||
if (instance.hasCwdPath()) {
|
||||
await fs.remove(instance.absoluteCwdPath());
|
||||
await fs.mkdirs(instance.absoluteCwdPath());
|
||||
}
|
||||
instance.println($t("TXT_CODE_1704ea49"), $t("TXT_CODE_906c5d6a"));
|
||||
this.process = new QuickInstallTask(
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { $t } from "../../../i18n";
|
||||
import logger from "../../../service/log";
|
||||
import Instance from "../../instance/instance";
|
||||
import InstanceCommand from "../base/command";
|
||||
@ -8,6 +9,16 @@ export default class GeneralKillCommand extends InstanceCommand {
|
||||
}
|
||||
|
||||
async exec(instance: Instance) {
|
||||
if (
|
||||
instance.status() === Instance.STATUS_STOP ||
|
||||
instance.status() === Instance.STATUS_STARTING
|
||||
) {
|
||||
return instance.failure(new Error($t("TXT_CODE_general_stop.notRunning")));
|
||||
}
|
||||
if (instance.startTimestamp + 6 * 1000 > Date.now()) {
|
||||
return instance.failure(new Error($t("TXT_CODE_6259357c")));
|
||||
}
|
||||
|
||||
const task = instance?.asynchronousTask;
|
||||
if (task && task.stop) {
|
||||
task
|
||||
@ -20,6 +31,5 @@ export default class GeneralKillCommand extends InstanceCommand {
|
||||
if (instance.process) {
|
||||
await instance.process.kill("SIGKILL");
|
||||
}
|
||||
instance.setLock(false);
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,14 @@ export default class GeneralRestartCommand extends InstanceCommand {
|
||||
}
|
||||
|
||||
async exec(instance: Instance) {
|
||||
// If the automatic restart function is enabled, the setting is ignored once
|
||||
if (instance.config.eventTask && instance.config.eventTask.autoRestart)
|
||||
instance.config.eventTask.ignore = true;
|
||||
|
||||
try {
|
||||
instance.println("INFO", $t("TXT_CODE_restart.start"));
|
||||
await instance.execPreset("stop");
|
||||
instance.setLock(true);
|
||||
await instance.execPreset("stop");
|
||||
const startCount = instance.startCount;
|
||||
// Check the instance status every second,
|
||||
// if the instance status is stopped, restart the server immediately
|
||||
@ -28,9 +32,9 @@ export default class GeneralRestartCommand extends InstanceCommand {
|
||||
}
|
||||
if (instance.status() === Instance.STATUS_STOP) {
|
||||
instance.println("INFO", $t("TXT_CODE_restart.restarting"));
|
||||
await instance.execPreset("start");
|
||||
instance.setLock(false);
|
||||
clearInterval(task);
|
||||
await instance.execPreset("start");
|
||||
}
|
||||
} catch (error: any) {
|
||||
clearInterval(task);
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { $t } from "../../../i18n";
|
||||
import os from "os";
|
||||
import Instance from "../../instance/instance";
|
||||
import logger from "../../../service/log";
|
||||
import fs from "fs-extra";
|
||||
import InstanceCommand from "../base/command";
|
||||
import EventEmitter from "events";
|
||||
import { IInstanceProcess } from "../../instance/interface";
|
||||
import { ChildProcess, exec, spawn } from "child_process";
|
||||
import { ChildProcess, spawn } from "child_process";
|
||||
import { commandStringToArray } from "../base/command_parser";
|
||||
import { killProcess } from "common";
|
||||
import AbsStartCommand from "../start";
|
||||
|
||||
// Error exception at startup
|
||||
class StartupError extends Error {
|
||||
@ -57,15 +56,15 @@ class ProcessAdapter extends EventEmitter implements IInstanceProcess {
|
||||
}
|
||||
}
|
||||
|
||||
export default class GeneralStartCommand extends InstanceCommand {
|
||||
export default class GeneralStartCommand extends AbsStartCommand {
|
||||
constructor() {
|
||||
super("StartCommand");
|
||||
}
|
||||
|
||||
async exec(instance: Instance, source = "Unknown") {
|
||||
async createProcess(instance: Instance, source = "") {
|
||||
if (
|
||||
(!instance.config.startCommand && instance.config.processType === "general") ||
|
||||
!instance.config.cwd ||
|
||||
!instance.hasCwdPath() ||
|
||||
!instance.config.ie ||
|
||||
!instance.config.oe
|
||||
)
|
||||
@ -85,13 +84,13 @@ export default class GeneralStartCommand extends InstanceCommand {
|
||||
logger.info($t("TXT_CODE_general_start.startInstance", { source: source }));
|
||||
logger.info($t("TXT_CODE_general_start.instanceUuid", { uuid: instance.instanceUuid }));
|
||||
logger.info($t("TXT_CODE_general_start.startCmd", { cmdList: JSON.stringify(commandList) }));
|
||||
logger.info($t("TXT_CODE_general_start.cwd", { cwd: instance.config.cwd }));
|
||||
logger.info($t("TXT_CODE_general_start.cwd", { cwd: instance.absoluteCwdPath() }));
|
||||
logger.info("----------------");
|
||||
|
||||
// create child process
|
||||
// Parameter 1 directly passes the process name or path (including spaces) without double quotes
|
||||
const subProcess = spawn(commandExeFile, commandParameters, {
|
||||
cwd: instance.config.cwd,
|
||||
cwd: instance.absoluteCwdPath(),
|
||||
stdio: "pipe",
|
||||
windowsHide: true,
|
||||
env: process.env
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { $t } from "../../../i18n";
|
||||
import Instance from "../../instance/instance";
|
||||
import InstanceCommand from "../base/command";
|
||||
import SendCommand from "../cmd";
|
||||
import RconCommand from "../steam/rcon_command";
|
||||
|
||||
export default class GeneralStopCommand extends InstanceCommand {
|
||||
constructor() {
|
||||
@ -10,6 +8,10 @@ export default class GeneralStopCommand extends InstanceCommand {
|
||||
}
|
||||
|
||||
async exec(instance: Instance) {
|
||||
// If the automatic restart function is enabled, the setting is ignored once
|
||||
if (instance.config.eventTask && instance.config.eventTask.autoRestart)
|
||||
instance.config.eventTask.ignore = true;
|
||||
|
||||
const stopCommand = instance.config.stopCommand;
|
||||
if (instance.status() === Instance.STATUS_STOP || !instance.process)
|
||||
return instance.failure(new Error($t("TXT_CODE_general_stop.notRunning")));
|
||||
@ -18,13 +20,7 @@ export default class GeneralStopCommand extends InstanceCommand {
|
||||
|
||||
const stopCommandList = stopCommand.split("\n");
|
||||
for (const stopCommand of stopCommandList) {
|
||||
if (stopCommand.toLowerCase() == "^c") {
|
||||
instance.process.kill("SIGINT");
|
||||
} else if (instance.config.enableRcon) {
|
||||
await instance.exec(new RconCommand(stopCommand));
|
||||
} else {
|
||||
await instance.exec(new SendCommand(stopCommand));
|
||||
}
|
||||
await instance.execPreset("command", stopCommand);
|
||||
}
|
||||
|
||||
instance.print("\n");
|
||||
|
@ -1,17 +0,0 @@
|
||||
import Instance from "../instance/instance";
|
||||
import InstanceCommand from "./base/command";
|
||||
|
||||
export default class KillCommand extends InstanceCommand {
|
||||
constructor() {
|
||||
super("KillCommand");
|
||||
}
|
||||
|
||||
async exec(instance: Instance) {
|
||||
// If the automatic restart function is enabled, the setting is ignored once
|
||||
if (instance.config.eventTask && instance.config.eventTask.autoRestart)
|
||||
instance.config.eventTask.ignore = true;
|
||||
|
||||
// send stop command
|
||||
return await instance.execPreset("kill");
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ import logger from "../../../service/log";
|
||||
import fs from "fs-extra";
|
||||
import path from "path";
|
||||
import readline from "readline";
|
||||
import InstanceCommand from "../base/command";
|
||||
import EventEmitter from "events";
|
||||
import { IInstanceProcess } from "../../instance/interface";
|
||||
import { ChildProcess, ChildProcessWithoutNullStreams, exec, spawn } from "child_process";
|
||||
@ -15,6 +14,7 @@ import FunctionDispatcher from "../dispatcher";
|
||||
import { PTY_PATH } from "../../../const";
|
||||
import { Writable } from "stream";
|
||||
import { v4 } from "uuid";
|
||||
import AbsStartCommand from "../start";
|
||||
|
||||
interface IPtySubProcessCfg {
|
||||
pid: number;
|
||||
@ -44,14 +44,17 @@ export class GoPtyProcessAdapter extends EventEmitter implements IInstanceProces
|
||||
process.stdout?.on("data", (text) => this.emit("data", text));
|
||||
process.stderr?.on("data", (text) => this.emit("data", text));
|
||||
process.on("exit", (code) => this.emit("exit", code));
|
||||
try {
|
||||
this.initNamedPipe();
|
||||
} catch (error: any) {
|
||||
logger.error(`Init Pipe Err: ${pipeName}, ${error}`);
|
||||
}
|
||||
this.initNamedPipe();
|
||||
}
|
||||
|
||||
private initNamedPipe() {
|
||||
if (!fs.existsSync(this.pipeName)) {
|
||||
throw new Error(
|
||||
$t("TXT_CODE_9d1d244f", {
|
||||
pipeName: this.pipeName
|
||||
})
|
||||
);
|
||||
}
|
||||
const fd = fs.openSync(this.pipeName, "w");
|
||||
const writePipe = fs.createWriteStream("", { fd });
|
||||
writePipe.on("close", () => {});
|
||||
@ -111,7 +114,7 @@ export class GoPtyProcessAdapter extends EventEmitter implements IInstanceProces
|
||||
}
|
||||
}
|
||||
|
||||
export default class PtyStartCommand extends InstanceCommand {
|
||||
export default class PtyStartCommand extends AbsStartCommand {
|
||||
constructor() {
|
||||
super("PtyStartCommand");
|
||||
}
|
||||
@ -141,21 +144,21 @@ export default class PtyStartCommand extends InstanceCommand {
|
||||
});
|
||||
}
|
||||
|
||||
async exec(instance: Instance, source = "Unknown") {
|
||||
async createProcess(instance: Instance) {
|
||||
if (
|
||||
!instance.config.startCommand ||
|
||||
!instance.config.cwd ||
|
||||
!instance.hasCwdPath() ||
|
||||
!instance.config.ie ||
|
||||
!instance.config.oe
|
||||
)
|
||||
throw new StartupError($t("TXT_CODE_pty_start.cmdErr"));
|
||||
if (!fs.existsSync(instance.absoluteCwdPath()))
|
||||
throw new StartupError($t("TXT_CODE_pty_start.cwdNotExist"));
|
||||
if (!path.isAbsolute(path.normalize(instance.config.cwd)))
|
||||
if (!path.isAbsolute(path.normalize(instance.absoluteCwdPath())))
|
||||
throw new StartupError($t("TXT_CODE_pty_start.mustAbsolutePath"));
|
||||
|
||||
// PTY mode correctness check
|
||||
logger.info($t("TXT_CODE_pty_start.startPty", { source: source }));
|
||||
logger.info($t("TXT_CODE_pty_start.startPty", { source: "" }));
|
||||
let checkPtyEnv = true;
|
||||
|
||||
if (!fs.existsSync(PTY_PATH)) {
|
||||
@ -167,12 +170,11 @@ export default class PtyStartCommand extends InstanceCommand {
|
||||
// Close the PTY type, reconfigure the instance function group, and restart the instance
|
||||
instance.config.terminalOption.pty = false;
|
||||
await instance.forceExec(new FunctionDispatcher());
|
||||
await instance.execPreset("start", source); // execute the preset command directly
|
||||
await instance.execPreset("start"); // execute the preset command directly
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the startup state & increase the number of startups
|
||||
instance.setLock(true);
|
||||
instance.status(Instance.STATUS_STARTING);
|
||||
instance.startCount++;
|
||||
|
||||
@ -202,7 +204,7 @@ export default class PtyStartCommand extends InstanceCommand {
|
||||
"-coder",
|
||||
instance.config.oe,
|
||||
"-dir",
|
||||
instance.config.cwd,
|
||||
instance.absoluteCwdPath(),
|
||||
"-fifo",
|
||||
pipeName,
|
||||
"-cmd",
|
||||
@ -210,12 +212,12 @@ export default class PtyStartCommand extends InstanceCommand {
|
||||
];
|
||||
|
||||
logger.info("----------------");
|
||||
logger.info($t("TXT_CODE_pty_start.sourceRequest", { source: source }));
|
||||
logger.info($t("TXT_CODE_pty_start.sourceRequest", { source: "" }));
|
||||
logger.info($t("TXT_CODE_pty_start.instanceUuid", { instanceUuid: instance.instanceUuid }));
|
||||
logger.info($t("TXT_CODE_pty_start.startCmd", { cmd: commandList.join(" ") }));
|
||||
logger.info($t("TXT_CODE_pty_start.ptyPath", { path: PTY_PATH }));
|
||||
logger.info($t("TXT_CODE_pty_start.ptyParams", { param: ptyParameter.join(" ") }));
|
||||
logger.info($t("TXT_CODE_pty_start.ptyCwd", { cwd: instance.config.cwd }));
|
||||
logger.info($t("TXT_CODE_pty_start.ptyCwd", { cwd: instance.absoluteCwdPath() }));
|
||||
logger.info("----------------");
|
||||
|
||||
// create pty child process
|
||||
@ -246,7 +248,6 @@ export default class PtyStartCommand extends InstanceCommand {
|
||||
// create process adapter
|
||||
const ptySubProcessCfg = await this.readPtySubProcessConfig(subProcess);
|
||||
const processAdapter = new GoPtyProcessAdapter(subProcess, ptySubProcessCfg.pid, pipeName);
|
||||
logger.info(`pty.exe subprocess PID: ${JSON.stringify(ptySubProcessCfg)}`);
|
||||
|
||||
// After reading the configuration, Need to check the process status
|
||||
// The "processAdapter.pid" here represents the process created by the PTY process
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { $t } from "../../../i18n";
|
||||
import Instance from "../../instance/instance";
|
||||
import InstanceCommand from "../base/command";
|
||||
import SendCommand from "../cmd";
|
||||
|
||||
export default class PtyStopCommand extends InstanceCommand {
|
||||
constructor() {
|
||||
@ -18,12 +17,8 @@ export default class PtyStopCommand extends InstanceCommand {
|
||||
instance.println("INFO", $t("TXT_CODE_pty_stop.execCmd", { stopCommand: stopCommand }));
|
||||
|
||||
const stopCommandList = stopCommand.split("\n");
|
||||
for (const stopCommandColumn of stopCommandList) {
|
||||
if (stopCommandColumn.toLocaleLowerCase() == "^c") {
|
||||
await instance.exec(new SendCommand("\x03"));
|
||||
} else {
|
||||
await instance.exec(new SendCommand(stopCommandColumn));
|
||||
}
|
||||
for (const stopCommand of stopCommandList) {
|
||||
await instance.execPreset("command", stopCommand);
|
||||
}
|
||||
|
||||
// If the instance is still in the stopped state after 10 minutes, restore the state
|
||||
|
@ -1,21 +0,0 @@
|
||||
import { $t } from "../../i18n";
|
||||
import Instance from "../instance/instance";
|
||||
import InstanceCommand from "./base/command";
|
||||
|
||||
export default class RestartCommand extends InstanceCommand {
|
||||
constructor() {
|
||||
super("RestartCommand");
|
||||
}
|
||||
|
||||
async exec(instance: Instance) {
|
||||
// If the automatic restart function is enabled, the setting is ignored once
|
||||
if (instance.config.eventTask && instance.config.eventTask.autoRestart)
|
||||
instance.config.eventTask.ignore = true;
|
||||
|
||||
if (instance.status() !== Instance.STATUS_RUNNING) {
|
||||
throw new Error($t("TXT_CODE_d58ffa0f"));
|
||||
}
|
||||
|
||||
return await instance.execPreset("restart");
|
||||
}
|
||||
}
|
@ -2,23 +2,16 @@ import { $t } from "../../i18n";
|
||||
import Instance from "../instance/instance";
|
||||
import InstanceCommand from "./base/command";
|
||||
|
||||
class StartupError extends Error {
|
||||
export class StartupError extends Error {
|
||||
constructor(msg: string) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export default class StartCommand extends InstanceCommand {
|
||||
public source: string;
|
||||
|
||||
constructor(source = "Unknown") {
|
||||
super("StartCommand");
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
export default abstract class AbsStartCommand extends InstanceCommand {
|
||||
private async sleep() {
|
||||
return new Promise((ok) => {
|
||||
setTimeout(ok, 1000 * 3);
|
||||
setTimeout(ok, 1000 * 2);
|
||||
});
|
||||
}
|
||||
|
||||
@ -41,16 +34,16 @@ export default class StartCommand extends InstanceCommand {
|
||||
}
|
||||
}
|
||||
|
||||
const currentTimestamp = Date.now();
|
||||
instance.startTimestamp = currentTimestamp;
|
||||
|
||||
instance.print("\n");
|
||||
instance.print("\n\n");
|
||||
instance.println("INFO", $t("TXT_CODE_start.startInstance"));
|
||||
|
||||
// prevent the dead-loop from starting
|
||||
await this.sleep();
|
||||
|
||||
return await instance.execPreset("start", this.source);
|
||||
const currentTimestamp = Date.now();
|
||||
instance.startTimestamp = currentTimestamp;
|
||||
|
||||
return await this.createProcess(instance);
|
||||
} catch (error: any) {
|
||||
try {
|
||||
await instance.execPreset("kill");
|
||||
@ -62,4 +55,6 @@ export default class StartCommand extends InstanceCommand {
|
||||
instance.setLock(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract createProcess(instance: Instance): Promise<void>;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { t } from "i18next";
|
||||
import Instance from "../../instance/instance";
|
||||
import InstanceCommand from "../base/command";
|
||||
import Rcon from "rcon-srcds";
|
||||
import { isExitCommand } from "../general/general_command";
|
||||
|
||||
async function sendRconCommand(instance: Instance, command: string) {
|
||||
const targetIp = instance.config.rconIp || "localhost";
|
||||
@ -45,6 +46,7 @@ export default class RconCommand extends InstanceCommand {
|
||||
}
|
||||
|
||||
async exec(instance: Instance, text?: string): Promise<any> {
|
||||
if (isExitCommand(instance, text)) return;
|
||||
try {
|
||||
if (text || this.cmd) {
|
||||
await sendRconCommand(instance, String(text ?? this.cmd));
|
||||
|
@ -1,18 +0,0 @@
|
||||
import Instance from "../instance/instance";
|
||||
import InstanceCommand from "./base/command";
|
||||
import SendCommand from "./cmd";
|
||||
|
||||
export default class StopCommand extends InstanceCommand {
|
||||
constructor() {
|
||||
super("StopCommand");
|
||||
}
|
||||
|
||||
async exec(instance: Instance) {
|
||||
// If the automatic restart function is enabled, the setting is ignored once
|
||||
if (instance.config.eventTask && instance.config.eventTask.autoRestart)
|
||||
instance.config.eventTask.ignore = true;
|
||||
|
||||
// send stop command
|
||||
return await instance.execPreset("stop");
|
||||
}
|
||||
}
|
@ -1,12 +1,8 @@
|
||||
import { v4 } from "uuid";
|
||||
import fs from "fs-extra";
|
||||
import path from "path";
|
||||
import { spawn, ChildProcess } from "child_process";
|
||||
import os from "os";
|
||||
import { killProcess } from "common";
|
||||
import { ILifeCycleTask } from "../../instance/life_cycle";
|
||||
import Instance from "../../instance/instance";
|
||||
import KillCommand from "../kill";
|
||||
import logger from "../../../service/log";
|
||||
import { $t } from "../../../i18n";
|
||||
import { ProcessWrapper } from "common";
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { ILifeCycleTask } from "../../instance/life_cycle";
|
||||
import Instance from "../../instance/instance";
|
||||
import KillCommand from "../kill";
|
||||
|
||||
// When the instance is running, continue to check the expiration time
|
||||
export default class TimeCheck implements ILifeCycleTask {
|
||||
@ -17,7 +16,7 @@ export default class TimeCheck implements ILifeCycleTask {
|
||||
const currentTime = Date.now();
|
||||
if (endTime <= currentTime) {
|
||||
// Expired, execute the end process command
|
||||
await instance.exec(new KillCommand());
|
||||
await instance.execPreset("kill");
|
||||
clearInterval(this.task);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
import Instance from "../instance/instance";
|
||||
import InstanceCommand from "./base/command";
|
||||
import SendCommand from "./cmd";
|
||||
|
||||
export default class UpdateCommand extends InstanceCommand {
|
||||
constructor() {
|
||||
super("UpdateCommand");
|
||||
}
|
||||
|
||||
async exec(instance: Instance) {
|
||||
// Execute the update preset, the preset and function scheduler are set before starting
|
||||
return await instance.execPreset("update");
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ import { PresetCommandManager } from "./preset";
|
||||
import FunctionDispatcher, { IPresetCommand } from "../commands/dispatcher";
|
||||
import { IInstanceProcess } from "./interface";
|
||||
import StartCommand from "../commands/start";
|
||||
import { configureEntityParams } from "common";
|
||||
import { configureEntityParams, toText } from "common";
|
||||
import { OpenFrp } from "../commands/task/openfrp";
|
||||
import logger from "../../service/log";
|
||||
import { t } from "i18next";
|
||||
@ -228,23 +228,12 @@ export default class Instance extends EventEmitter {
|
||||
}
|
||||
|
||||
setLock(bool: boolean) {
|
||||
if (this.lock === true && bool === true) {
|
||||
throw new Error($t("TXT_CODE_ca030197"));
|
||||
}
|
||||
this.lock = bool;
|
||||
}
|
||||
|
||||
// Execute the corresponding command for this instance
|
||||
async execCommand(command: InstanceCommand) {
|
||||
if (this.lock)
|
||||
throw new Error($t("TXT_CODE_instanceConf.instanceLock", { info: command.info }));
|
||||
if (this.status() == Instance.STATUS_BUSY)
|
||||
throw new Error($t("TXT_CODE_instanceConf.instanceBusy"));
|
||||
return await command.exec(this);
|
||||
}
|
||||
|
||||
// Execute the corresponding command for this instance Alias
|
||||
async exec(command: InstanceCommand) {
|
||||
return await this.execCommand(command);
|
||||
}
|
||||
|
||||
// force the command to execute
|
||||
async forceExec(command: InstanceCommand) {
|
||||
return await command.exec(this);
|
||||
@ -299,7 +288,7 @@ export default class Instance extends EventEmitter {
|
||||
// If automatic restart is enabled, the startup operation is performed immediately
|
||||
if (this.config.eventTask.autoRestart) {
|
||||
if (!this.config.eventTask.ignore) {
|
||||
this.forceExec(new StartCommand("Event Task: Auto Restart"))
|
||||
this.execPreset("start")
|
||||
.then(() => {
|
||||
this.println($t("TXT_CODE_instanceConf.info"), $t("TXT_CODE_instanceConf.autoRestart"));
|
||||
})
|
||||
@ -315,9 +304,9 @@ export default class Instance extends EventEmitter {
|
||||
|
||||
// Turn off the warning immediately after startup, usually the startup command is written incorrectly
|
||||
const currentTimestamp = new Date().getTime();
|
||||
const startThreshold = 6 * 1000;
|
||||
const startThreshold = 2 * 1000;
|
||||
if (currentTimestamp - this.startTimestamp < startThreshold) {
|
||||
this.println("ERROR", $t("TXT_CODE_instanceConf.instantExit"));
|
||||
this.println("ERROR", $t("TXT_CODE_aae2918f"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -356,6 +345,10 @@ export default class Instance extends EventEmitter {
|
||||
return date.toLocaleDateString() + " " + date.getHours() + ":" + date.getMinutes();
|
||||
}
|
||||
|
||||
hasCwdPath() {
|
||||
return !!this.config.cwd;
|
||||
}
|
||||
|
||||
absoluteCwdPath() {
|
||||
if (!this.config || !this.config.cwd) throw new Error("Instance config error, cwd is Null!");
|
||||
if (path.isAbsolute(this.config.cwd)) return path.normalize(this.config.cwd);
|
||||
@ -406,6 +399,13 @@ export default class Instance extends EventEmitter {
|
||||
this.info.latency = 0;
|
||||
}
|
||||
|
||||
public parseTextParams(text: string) {
|
||||
text = text.replace(/\{mcsm_workspace\}/gim, this.absoluteCwdPath());
|
||||
text = text.replace(/\{mcsm_instance_id\}/gim, this.instanceUuid);
|
||||
text = text.replace(/\{mcsm_cwd\}/gim, this.absoluteCwdPath());
|
||||
return text;
|
||||
}
|
||||
|
||||
private pushOutput(data: string) {
|
||||
if (data.length > LINE_MAX_SIZE * 100) {
|
||||
this.outputStack.push(IGNORE_TEXT);
|
||||
|
@ -7,19 +7,14 @@ import Instance from "../entity/instance/instance";
|
||||
import logger from "../service/log";
|
||||
import path from "path";
|
||||
|
||||
import StartCommand from "../entity/commands/start";
|
||||
import StopCommand from "../entity/commands/stop";
|
||||
import SendCommand from "../entity/commands/cmd";
|
||||
import KillCommand from "../entity/commands/kill";
|
||||
import { IInstanceDetail, IJson } from "../service/interfaces";
|
||||
import ProcessInfoCommand from "../entity/commands/process_info";
|
||||
import FileManager from "../service/system_file";
|
||||
import { ProcessConfig } from "../entity/instance/process_config";
|
||||
import RestartCommand from "../entity/commands/restart";
|
||||
import { TaskCenter } from "../service/async_task_service";
|
||||
import { createQuickInstallTask } from "../service/async_task_service/quick_install";
|
||||
import { QuickInstallTask } from "../service/async_task_service/quick_install";
|
||||
import { toNumber, toText } from "common";
|
||||
import { toNumber } from "common";
|
||||
import { arrayUnique } from "common";
|
||||
|
||||
// Some instances operate router authentication middleware
|
||||
@ -221,7 +216,7 @@ routerApp.on("instance/open", async (ctx, data) => {
|
||||
for (const instanceUuid of data.instanceUuids) {
|
||||
const instance = InstanceSubsystem.getInstance(instanceUuid);
|
||||
try {
|
||||
await instance!.exec(new StartCommand(ctx.socket.id));
|
||||
await instance!.execPreset("start");
|
||||
if (!disableResponse) protocol.msg(ctx, "instance/open", { instanceUuid });
|
||||
} catch (err: any) {
|
||||
if (!disableResponse) {
|
||||
@ -242,7 +237,7 @@ routerApp.on("instance/stop", async (ctx, data) => {
|
||||
const instance = InstanceSubsystem.getInstance(instanceUuid);
|
||||
try {
|
||||
if (!instance) throw new Error($t("TXT_CODE_3bfb9e04"));
|
||||
await instance.exec(new StopCommand());
|
||||
await instance.execPreset("stop");
|
||||
//Note: Removing this reply will cause the front-end response to be slow, because the front-end will wait for the panel-side message to be forwarded
|
||||
if (!disableResponse) protocol.msg(ctx, "instance/stop", { instanceUuid });
|
||||
} catch (err: any) {
|
||||
@ -259,7 +254,7 @@ routerApp.on("instance/restart", async (ctx, data) => {
|
||||
const instance = InstanceSubsystem.getInstance(instanceUuid);
|
||||
try {
|
||||
if (!instance) throw new Error($t("TXT_CODE_3bfb9e04"));
|
||||
await instance.exec(new RestartCommand());
|
||||
await instance.execPreset("restart");
|
||||
if (!disableResponse) protocol.msg(ctx, "instance/restart", { instanceUuid });
|
||||
} catch (err: any) {
|
||||
if (!disableResponse)
|
||||
@ -275,7 +270,7 @@ routerApp.on("instance/kill", async (ctx, data) => {
|
||||
const instance = InstanceSubsystem.getInstance(instanceUuid);
|
||||
if (!instance) continue;
|
||||
try {
|
||||
await instance.forceExec(new KillCommand());
|
||||
await instance.execPreset("kill");
|
||||
if (!disableResponse) protocol.msg(ctx, "instance/kill", { instanceUuid });
|
||||
} catch (err: any) {
|
||||
if (!disableResponse)
|
||||
@ -292,7 +287,7 @@ routerApp.on("instance/command", async (ctx, data) => {
|
||||
const instance = InstanceSubsystem.getInstance(instanceUuid);
|
||||
try {
|
||||
if (!instance) throw new Error($t("TXT_CODE_3bfb9e04"));
|
||||
await instance.exec(new SendCommand(command));
|
||||
await instance.execPreset("command", command);
|
||||
if (!disableResponse) protocol.msg(ctx, "instance/command", { instanceUuid });
|
||||
} catch (err: any) {
|
||||
if (!disableResponse)
|
||||
|
@ -31,7 +31,7 @@ router.get("/download/:key/:fileName", async (ctx) => {
|
||||
if (!FileManager.checkFileName(paramsFileName))
|
||||
throw new Error($t("TXT_CODE_http_router.fileNameNotSpec"));
|
||||
|
||||
const cwd = instance.config.cwd;
|
||||
const cwd = instance.absoluteCwdPath();
|
||||
const fileRelativePath = mission.parameter.fileName;
|
||||
|
||||
// Check for file cross-directory security risks
|
||||
@ -65,7 +65,7 @@ router.post("/upload/:key", async (ctx) => {
|
||||
const instance = InstanceSubsystem.getInstance(mission.parameter.instanceUuid);
|
||||
if (!instance) throw new Error("Access denied: No instance found");
|
||||
const uploadDir = mission.parameter.uploadDir;
|
||||
const cwd = instance.config.cwd;
|
||||
const cwd = instance.absoluteCwdPath();
|
||||
const tmpFiles = ctx.request.files?.file;
|
||||
if (tmpFiles) {
|
||||
let uploadedFile: formidable.File;
|
||||
@ -120,7 +120,7 @@ router.post("/upload/:key", async (ctx) => {
|
||||
});
|
||||
|
||||
if (unzip) {
|
||||
const fileManager = new FileManager(instance.config.cwd);
|
||||
const fileManager = new FileManager(instance.absoluteCwdPath());
|
||||
fileManager.unzip(fileSaveAbsolutePath, "", zipCode);
|
||||
}
|
||||
ctx.body = "OK";
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
streamLoginSuccessful
|
||||
} from "../service/mission_passport";
|
||||
import InstanceSubsystem from "../service/system_instance";
|
||||
import SendCommand from "../entity/commands/cmd";
|
||||
import { IGNORE } from "../const";
|
||||
|
||||
// Authorization authentication middleware
|
||||
@ -79,7 +78,7 @@ routerApp.on("stream/input", async (ctx, data) => {
|
||||
const command = data.command;
|
||||
const instanceUuid = ctx.session?.stream?.instanceUuid;
|
||||
const instance = InstanceSubsystem.getInstance(instanceUuid);
|
||||
await instance?.exec(new SendCommand(command));
|
||||
await instance?.execPreset("command", command);
|
||||
} catch (error: any) {
|
||||
// Ignore potential high frequency exceptions here
|
||||
}
|
||||
|
@ -57,7 +57,9 @@ export class QuickInstallTask extends AsyncTask {
|
||||
const url = new URL(this.targetLink);
|
||||
downloadFileName = url.pathname.split("/").pop() || `application${this.extName}`;
|
||||
}
|
||||
this.filePath = path.normalize(path.join(this.instance.config.cwd, downloadFileName));
|
||||
this.filePath = path.normalize(
|
||||
path.join(this.instance.absoluteCwdPath(), downloadFileName)
|
||||
);
|
||||
this.writeStream = fs.createWriteStream(this.filePath);
|
||||
const response = await axios<Readable>({
|
||||
url: this.targetLink,
|
||||
@ -109,7 +111,7 @@ export class QuickInstallTask extends AsyncTask {
|
||||
let startCommand = this.instance.config.startCommand;
|
||||
const ENV_MAP: IJsonData = {
|
||||
java: "java",
|
||||
cwd: this.instance.config.cwd,
|
||||
cwd: this.instance.absoluteCwdPath(),
|
||||
rconIp: this.instance.config.rconIp || "localhost",
|
||||
rconPort: String(this.instance.config.rconPort),
|
||||
rconPassword: this.instance.config.rconPassword,
|
||||
|
@ -12,6 +12,7 @@ import { EventEmitter } from "stream";
|
||||
import { IInstanceProcess } from "../entity/instance/interface";
|
||||
import { AsyncTask } from "./async_task_service";
|
||||
import iconv from "iconv-lite";
|
||||
import { toText } from "common";
|
||||
|
||||
// Error exception at startup
|
||||
export class StartupDockerProcessError extends Error {
|
||||
@ -51,8 +52,6 @@ export class SetupDockerContainer extends AsyncTask {
|
||||
commandList = [];
|
||||
}
|
||||
|
||||
const cwd = instance.absoluteCwdPath();
|
||||
|
||||
// Parsing port open
|
||||
// 25565:25565/tcp 8080:8080/tcp
|
||||
const portMap = instance.config.docker.ports || [];
|
||||
@ -119,7 +118,18 @@ export class SetupDockerContainer extends AsyncTask {
|
||||
|
||||
const workingDir = instance.config.docker.workingDir ?? "";
|
||||
|
||||
// output startup log
|
||||
let cwd = instance.absoluteCwdPath();
|
||||
const hostRealPath = toText(process.env.MCSM_DOCKER_WORKSPACE_PATH);
|
||||
if (hostRealPath) {
|
||||
cwd = path.normalize(path.join(hostRealPath, instance.instanceUuid));
|
||||
}
|
||||
|
||||
if (workingDir) {
|
||||
instance.println("CONTAINER", $t("TXT_CODE_e76e49e9") + cwd + " --> " + workingDir + "\n");
|
||||
} else {
|
||||
instance.println("CONTAINER", $t("TXT_CODE_ffa884f9"));
|
||||
}
|
||||
|
||||
logger.info("----------------");
|
||||
logger.info(`[SetupDockerContainer]`);
|
||||
logger.info(`UUID: [${instance.instanceUuid}] [${instance.config.nickname}]`);
|
||||
@ -130,7 +140,7 @@ export class SetupDockerContainer extends AsyncTask {
|
||||
logger.info(`OPEN_PORT: ${JSON.stringify(publicPortArray)}`);
|
||||
logger.info(
|
||||
`BINDS: ${JSON.stringify([
|
||||
workingDir ? `${cwd}->${workingDir}` : "<No WorkSpace>",
|
||||
workingDir ? `${cwd} --> ${workingDir}` : "<Working directory not mounted>",
|
||||
...extraBinds
|
||||
])}`
|
||||
);
|
||||
@ -139,6 +149,22 @@ export class SetupDockerContainer extends AsyncTask {
|
||||
logger.info(`TYPE: Docker Container`);
|
||||
logger.info("----------------");
|
||||
|
||||
const mounts: Docker.MountConfig =
|
||||
extraBinds.map((v) => {
|
||||
return {
|
||||
Type: "bind",
|
||||
Source: instance.parseTextParams(v.hostPath),
|
||||
Target: instance.parseTextParams(v.containerPath)
|
||||
};
|
||||
}) || [];
|
||||
if (workingDir && cwd) {
|
||||
mounts.push({
|
||||
Type: "bind",
|
||||
Source: cwd,
|
||||
Target: workingDir
|
||||
});
|
||||
}
|
||||
|
||||
// Start Docker container creation and running
|
||||
const docker = new DefaultDocker();
|
||||
this.container = await docker.createContainer({
|
||||
@ -163,20 +189,7 @@ export class SetupDockerContainer extends AsyncTask {
|
||||
CpuQuota: cpuQuota,
|
||||
PortBindings: publicPortArray,
|
||||
NetworkMode: instance.config.docker.networkMode,
|
||||
Mounts: [
|
||||
{
|
||||
Type: "bind",
|
||||
Source: cwd,
|
||||
Target: workingDir
|
||||
},
|
||||
...extraBinds.map((v) => {
|
||||
return {
|
||||
Type: "bind" as Docker.MountType,
|
||||
Source: v.hostPath,
|
||||
Target: v.containerPath
|
||||
};
|
||||
})
|
||||
]
|
||||
Mounts: mounts
|
||||
},
|
||||
NetworkingConfig: {
|
||||
EndpointsConfig: {
|
||||
|
@ -10,8 +10,7 @@ export function getFileManager(instanceUuid: string) {
|
||||
if (!instance)
|
||||
throw new Error($t("TXT_CODE_file_router_service.instanceNotExit", { uuid: instanceUuid }));
|
||||
const fileCode = instance.config?.fileCode;
|
||||
const cwd = instance.config.cwd;
|
||||
return new FileManager(cwd, fileCode);
|
||||
return new FileManager(instance.absoluteCwdPath(), fileCode);
|
||||
}
|
||||
|
||||
let cacheDisks: string[] = [];
|
||||
|
@ -19,8 +19,7 @@ export class InstanceUpdateAction extends AsyncTask {
|
||||
}
|
||||
|
||||
public async onStart() {
|
||||
let updateCommand = this.instance.config.updateCommand;
|
||||
updateCommand = updateCommand.replace(/\{mcsm_workspace\}/gm, this.instance.config.cwd);
|
||||
const updateCommand = this.instance.parseTextParams(this.instance.config.updateCommand);
|
||||
logger.info(
|
||||
$t("TXT_CODE_general_update.readyUpdate", { instanceUuid: this.instance.instanceUuid })
|
||||
);
|
||||
@ -29,6 +28,7 @@ export class InstanceUpdateAction extends AsyncTask {
|
||||
);
|
||||
logger.info(updateCommand);
|
||||
|
||||
this.instance.print("\n");
|
||||
this.instance.println(
|
||||
$t("TXT_CODE_general_update.update"),
|
||||
$t("TXT_CODE_general_update.readyUpdate", { instanceUuid: this.instance.instanceUuid })
|
||||
@ -57,7 +57,7 @@ export class InstanceUpdateAction extends AsyncTask {
|
||||
|
||||
// start the update command
|
||||
const process = spawn(commandExeFile, commandParameters, {
|
||||
cwd: this.instance.config.cwd,
|
||||
cwd: this.instance.absoluteCwdPath(),
|
||||
stdio: "pipe",
|
||||
windowsHide: true
|
||||
});
|
||||
|
@ -4,7 +4,6 @@ import path from "path";
|
||||
import os from "os";
|
||||
import Instance from "../entity/instance/instance";
|
||||
import EventEmitter from "events";
|
||||
import KillCommand from "../entity/commands/kill";
|
||||
import logger from "./log";
|
||||
import { v4 } from "uuid";
|
||||
import { Socket } from "socket.io";
|
||||
@ -45,7 +44,7 @@ class InstanceSubsystem extends EventEmitter {
|
||||
this.instances.forEach((instance) => {
|
||||
if (instance.config.eventTask.autoStart) {
|
||||
instance
|
||||
.exec(new StartCommand())
|
||||
.execPreset("start")
|
||||
.then(() => {
|
||||
logger.info(
|
||||
$t("TXT_CODE_system_instance.autoStart", {
|
||||
@ -180,7 +179,7 @@ class InstanceSubsystem extends EventEmitter {
|
||||
this.instances.delete(instanceUuid);
|
||||
StorageSubsystem.delete("InstanceConfig", instanceUuid);
|
||||
InstanceControl.deleteInstanceAllTask(instanceUuid);
|
||||
if (deleteFile) fs.remove(instance.config.cwd, (err) => {});
|
||||
if (deleteFile) fs.remove(instance.absoluteCwdPath(), (err) => {});
|
||||
return true;
|
||||
}
|
||||
throw new Error($t("TXT_CODE_3bfb9e04"));
|
||||
@ -228,7 +227,7 @@ class InstanceSubsystem extends EventEmitter {
|
||||
`Instance ${instance.config.nickname} (${instance.instanceUuid}) is running or busy, and is being forced to end.`
|
||||
);
|
||||
promises.push(
|
||||
instance.execCommand(new KillCommand()).then(() => {
|
||||
instance.execPreset("kill").then(() => {
|
||||
if (!this.isGlobalInstance(instance))
|
||||
StorageSubsystem.store("InstanceConfig", instance.instanceUuid, instance.config);
|
||||
logger.info(
|
||||
|
@ -3,11 +3,6 @@ import schedule from "node-schedule";
|
||||
import InstanceSubsystem from "./system_instance";
|
||||
import StorageSubsystem from "../common/system_storage";
|
||||
import logger from "./log";
|
||||
import StartCommand from "../entity/commands/start";
|
||||
import StopCommand from "../entity/commands/stop";
|
||||
import SendCommand from "../entity/commands/cmd";
|
||||
import RestartCommand from "../entity/commands/restart";
|
||||
import KillCommand from "../entity/commands/kill";
|
||||
import FileManager from "./system_file";
|
||||
|
||||
// Scheduled task configuration item interface
|
||||
@ -170,26 +165,26 @@ class InstanceControlSubsystem {
|
||||
// logger.info(`Execute scheduled task: ${task.name} ${task.action} ${task.time} ${task.count} `);
|
||||
if (task.action === "start") {
|
||||
if (instanceStatus === 0) {
|
||||
return await instance.exec(new StartCommand("ScheduleJob"));
|
||||
return await instance.execPreset("start");
|
||||
}
|
||||
}
|
||||
if (task.action === "stop") {
|
||||
if (instanceStatus === 3) {
|
||||
return await instance.exec(new StopCommand());
|
||||
return await instance.execPreset("stop");
|
||||
}
|
||||
}
|
||||
if (task.action === "restart") {
|
||||
if (instanceStatus === 3) {
|
||||
return await instance.exec(new RestartCommand());
|
||||
return await instance.execPreset("restart");
|
||||
}
|
||||
}
|
||||
if (task.action === "command") {
|
||||
if (instanceStatus === 3) {
|
||||
return await instance.exec(new SendCommand(payload));
|
||||
return await instance.execPreset("command", payload);
|
||||
}
|
||||
}
|
||||
if (task.action === "kill") {
|
||||
return await instance.exec(new KillCommand());
|
||||
return await instance.execPreset("kill");
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error(
|
||||
|
@ -1,118 +0,0 @@
|
||||
import { $t } from "../i18n";
|
||||
import readline from "readline";
|
||||
|
||||
import * as protocol from "./protocol";
|
||||
import InstanceSubsystem from "./system_instance";
|
||||
import { globalConfiguration } from "../entity/config";
|
||||
import logger from "./log";
|
||||
import StartCommand from "../entity/commands/start";
|
||||
import StopCommand from "../entity/commands/stop";
|
||||
import KillCommand from "../entity/commands/kill";
|
||||
import SendCommand from "../entity/commands/cmd";
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
console.log($t("TXT_CODE_ui.help"));
|
||||
|
||||
function stdin() {
|
||||
rl.question("> ", async (answer) => {
|
||||
try {
|
||||
const cmds = answer.split(" ");
|
||||
logger.info(`[Terminal] ${answer}`);
|
||||
const result = await command(cmds[0], cmds[1], cmds[2], cmds[3]);
|
||||
if (result) console.log(result);
|
||||
else console.log(`Command ${answer} does not exist, type help to get help.`);
|
||||
} catch (err) {
|
||||
logger.error("[Terminal]", err);
|
||||
} finally {
|
||||
// next
|
||||
stdin();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stdin();
|
||||
|
||||
/**
|
||||
* Pass in relevant UI commands and output command results
|
||||
* @param {String} cmd
|
||||
* @return {String}
|
||||
*/
|
||||
async function command(cmd: string, p1: string, p2: string, p3: string) {
|
||||
if (cmd === "instance") {
|
||||
if (p1 === "start") {
|
||||
InstanceSubsystem?.getInstance(p2)?.exec(new StartCommand("Terminal"));
|
||||
return "Done.";
|
||||
}
|
||||
if (p1 === "stop") {
|
||||
InstanceSubsystem?.getInstance(p2)?.exec(new StopCommand());
|
||||
return "Done.";
|
||||
}
|
||||
if (p1 === "kill") {
|
||||
InstanceSubsystem?.getInstance(p2)?.exec(new KillCommand());
|
||||
return "Done.";
|
||||
}
|
||||
if (p1 === "send") {
|
||||
InstanceSubsystem?.getInstance(p2)?.exec(new SendCommand(p3));
|
||||
return "Done.";
|
||||
}
|
||||
return "Parameter error";
|
||||
}
|
||||
|
||||
if (cmd === "instances") {
|
||||
const objs = InstanceSubsystem.instances;
|
||||
let result = "instance name | instance UUID | status code\n";
|
||||
objs.forEach((v) => {
|
||||
result += `${v.config.nickname} ${v.instanceUuid} ${v.status()}\n`;
|
||||
});
|
||||
result += "\nStatus Explanation:\n Busy=-1;Stop=0;Stopping=1;Starting=2;Running=3;\n";
|
||||
return result;
|
||||
}
|
||||
|
||||
if (cmd === "sockets") {
|
||||
const sockets = protocol.socketObjects();
|
||||
let result = "IP address | identifier\n";
|
||||
sockets.forEach((v) => {
|
||||
result += `${v.handshake.address} ${v.id}\n`;
|
||||
});
|
||||
result += `Total ${sockets.size} online.\n`;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (cmd == "key") {
|
||||
return globalConfiguration.config.key;
|
||||
}
|
||||
|
||||
if (cmd == "exit") {
|
||||
try {
|
||||
logger.info("Preparing to shut down the daemon...");
|
||||
await InstanceSubsystem.exit();
|
||||
// logger.info("Data saved, thanks for using, goodbye!");
|
||||
logger.info("The data is saved, thanks for using, goodbye!");
|
||||
logger.info("closed.");
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
"Failed to end the program. Please check the file permissions and try again. If you still can't close it, please use Ctrl+C to close.",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd == "help") {
|
||||
console.log("----------- Help document -----------");
|
||||
console.log(" instances view all instances");
|
||||
console.log(" Sockets view all linkers");
|
||||
console.log(" key view key");
|
||||
console.log(" exit to close this program (recommended method)");
|
||||
console.log(" instance start <UUID> to start the specified instance");
|
||||
console.log(" instance stop <UUID> to start the specified instance");
|
||||
console.log(" instance kill <UUID> to start the specified instance");
|
||||
console.log(" instance send <UUID> <CMD> to send a command to the instance");
|
||||
console.log("----------- Help document -----------");
|
||||
return "\n";
|
||||
}
|
||||
}
|
36
dockerfile/daemon.dockerfile
Normal file
36
dockerfile/daemon.dockerfile
Normal file
@ -0,0 +1,36 @@
|
||||
ARG EMBEDDED_JAVA_VERSION=21
|
||||
ARG BUILDPLATFORM=linux/amd64
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} node:lts-alpine AS builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN apk add --no-cache wget &&\
|
||||
chmod a+x ./install-dependents.sh &&\
|
||||
chmod a+x ./build.sh &&\
|
||||
./install-dependents.sh &&\
|
||||
./build.sh &&\
|
||||
wget --input-file=lib-urls.txt --directory-prefix=production-code/daemon/lib/ &&\
|
||||
chmod a+x production-code/daemon/lib/*
|
||||
|
||||
FROM eclipse-temurin:${EMBEDDED_JAVA_VERSION}-jdk
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y curl &&\
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash &&\
|
||||
apt-get update && apt-get install -y nodejs && apt-get clean
|
||||
|
||||
WORKDIR /opt/mcsmanager/daemon
|
||||
|
||||
COPY --from=builder /src/production-code/daemon/ /opt/mcsmanager/daemon/
|
||||
|
||||
RUN npm install --production
|
||||
|
||||
EXPOSE 24444
|
||||
|
||||
ENV MCSM_INSTANCES_BASE_PATH=/opt/mcsmanager/daemon/data/InstanceData
|
||||
|
||||
VOLUME ["/opt/mcsmanager/daemon/data", "/opt/mcsmanager/daemon/logs"]
|
||||
|
||||
CMD [ "node", "app.js", "--max-old-space-size=8192" ]
|
24
dockerfile/web.dockerfile
Normal file
24
dockerfile/web.dockerfile
Normal file
@ -0,0 +1,24 @@
|
||||
ARG BUILDPLATFORM=linux/amd64
|
||||
FROM --platform=${BUILDPLATFORM} node:lts-alpine AS builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN chmod a+x ./install-dependents.sh &&\
|
||||
chmod a+x ./build.sh &&\
|
||||
./install-dependents.sh &&\
|
||||
./build.sh
|
||||
|
||||
FROM node:lts-alpine
|
||||
|
||||
WORKDIR /opt/mcsmanager/web
|
||||
|
||||
COPY --from=builder /src/production-code/web/ /opt/mcsmanager/web/
|
||||
|
||||
RUN npm install --production
|
||||
|
||||
EXPOSE 23333
|
||||
|
||||
VOLUME ["/opt/mcsmanager/web/data", "/opt/mcsmanager/web/logs"]
|
||||
|
||||
CMD [ "app.js", "--max-old-space-size=8192" ]
|
24
example.docker-compose.yml
Normal file
24
example.docker-compose.yml
Normal file
@ -0,0 +1,24 @@
|
||||
# docker-compose.yml
|
||||
services:
|
||||
web:
|
||||
image: githubyumao/mcsmanager-web:latest
|
||||
ports:
|
||||
- "23333:23333"
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- <CHANGE_ME_TO_INSTALL_PATH>/web/data:/opt/mcsmanager/web/data
|
||||
- <CHANGE_ME_TO_INSTALL_PATH>/web/logs:/opt/mcsmanager/web/logs
|
||||
|
||||
daemon:
|
||||
image: githubyumao/mcsmanager-daemon:latest
|
||||
restart: unless-stopped
|
||||
network_mode: host # if you want run instance in daemon container
|
||||
# ports:
|
||||
# - "24444:24444"
|
||||
environment:
|
||||
- MCSM_DOCKER_WORKSPACE_PATH=<CHANGE_ME_TO_INSTALL_PATH>/daemon/data/InstanceData
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- <CHANGE_ME_TO_INSTALL_PATH>/daemon/data:/opt/mcsmanager/daemon/data
|
||||
- <CHANGE_ME_TO_INSTALL_PATH>/daemon/logs:/opt/mcsmanager/daemon/logs
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
@ -2,7 +2,7 @@
|
||||
import { onMounted, ref } from "vue";
|
||||
import { t } from "@/lang/i18n";
|
||||
import { CodeOutlined, DeleteOutlined, LoadingOutlined } from "@ant-design/icons-vue";
|
||||
import { useTerminal } from "../hooks/useTerminal";
|
||||
import { encodeConsoleColor, useTerminal } from "../hooks/useTerminal";
|
||||
import { getInstanceOutputLog } from "@/services/apis/instance";
|
||||
import { message } from "ant-design-vue";
|
||||
import connectErrorImage from "@/assets/daemon_connection_error.png";
|
||||
@ -29,7 +29,7 @@ const {
|
||||
clickHistoryItem
|
||||
} = useCommandHistory();
|
||||
|
||||
const { execute, initTerminalWindow, sendCommand, events, isConnect, socketAddress } =
|
||||
const { execute, initTerminalWindow, sendCommand, state, events, isConnect, socketAddress } =
|
||||
useTerminal();
|
||||
|
||||
const instanceId = props.instanceId;
|
||||
@ -64,7 +64,14 @@ const initTerminal = async () => {
|
||||
const { value } = await getInstanceOutputLog().execute({
|
||||
params: { uuid: instanceId || "", daemonId: daemonId || "" }
|
||||
});
|
||||
if (value) term.write(value);
|
||||
|
||||
if (value) {
|
||||
if (state.value?.config?.terminalOption?.haveColor) {
|
||||
term.write(encodeConsoleColor(value));
|
||||
} else {
|
||||
term.write(value);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {}
|
||||
return term;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ interface Props extends MountComponent {
|
||||
keyTitle?: string;
|
||||
valueTitle?: string;
|
||||
data: any[];
|
||||
subTitle?: string;
|
||||
columns?: AntColumnsType[];
|
||||
}
|
||||
|
||||
@ -100,6 +101,10 @@ const operation = (type: "add" | "del", index = 0) => {
|
||||
@cancel="cancel"
|
||||
>
|
||||
<div class="dialog-overflow-container">
|
||||
<div v-if="props.subTitle" style="font-size: 13px" class="text-gray-400 mb-4">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-html="props.subTitle"></span>
|
||||
</div>
|
||||
<div class="flex justify-end mb-20">
|
||||
<a-button :icon="h(PlusCircleOutlined)" @click="operation('add')">
|
||||
{{ t("TXT_CODE_dfc17a0c") }}
|
||||
@ -132,7 +137,7 @@ const operation = (type: "add" | "del", index = 0) => {
|
||||
>
|
||||
<a-input
|
||||
v-model:value="record[String(column.dataIndex)]"
|
||||
:placeholder="t('TXT_CODE_4ea93630')"
|
||||
:placeholder="(column as any).placeholder"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
@ -65,17 +65,20 @@ export async function usePortEditDialog(data: PortConfigItem[] = []) {
|
||||
{
|
||||
align: "center",
|
||||
dataIndex: "host",
|
||||
title: t("TXT_CODE_534db0b2")
|
||||
title: t("TXT_CODE_534db0b2"),
|
||||
placeholder: "eg: 8080"
|
||||
},
|
||||
{
|
||||
align: "center",
|
||||
dataIndex: "container",
|
||||
title: t("TXT_CODE_b729d2e")
|
||||
title: t("TXT_CODE_b729d2e"),
|
||||
placeholder: "eg: 25565"
|
||||
},
|
||||
{
|
||||
align: "center",
|
||||
dataIndex: "protocol",
|
||||
title: t("TXT_CODE_ad1c674c")
|
||||
title: t("TXT_CODE_ad1c674c"),
|
||||
placeholder: "tcp/udp"
|
||||
}
|
||||
] as AntColumnsType[]
|
||||
}).mount<PortConfigItem[]>(KvOptionsDialogVue)) || []
|
||||
@ -86,6 +89,7 @@ export async function useVolumeEditDialog(data: DockerConfigItem[] = []) {
|
||||
return (
|
||||
(await useMountComponent({
|
||||
data,
|
||||
subTitle: t("TXT_CODE_6c232c9c"),
|
||||
title: t("TXT_CODE_820ebc92"),
|
||||
columns: [
|
||||
{
|
||||
|
@ -500,12 +500,17 @@ export const useFileManager = (instanceId?: string, daemonId?: string) => {
|
||||
const handleTableChange = (e: { current: number; pageSize: number }) => {
|
||||
selectedRowKeys.value = [];
|
||||
selectionData.value = [];
|
||||
operationForm.value.name = "";
|
||||
// operationForm.value.name = "";
|
||||
operationForm.value.current = e.current;
|
||||
operationForm.value.pageSize = e.pageSize;
|
||||
getFileList();
|
||||
};
|
||||
|
||||
const handleSearchChange = () =>{
|
||||
operationForm.value.current = 1;
|
||||
getFileList();
|
||||
}
|
||||
|
||||
const getFileStatus = async () => {
|
||||
const { state, execute } = getFileStatusApi();
|
||||
try {
|
||||
@ -627,6 +632,7 @@ export const useFileManager = (instanceId?: string, daemonId?: string) => {
|
||||
downloadFile,
|
||||
handleChangeDir,
|
||||
handleTableChange,
|
||||
handleSearchChange,
|
||||
getFileStatus,
|
||||
changePermission,
|
||||
toDisk,
|
||||
|
@ -1,9 +1,49 @@
|
||||
import { io } from "socket.io-client";
|
||||
import type { Socket } from "socket.io-client";
|
||||
import type { DefaultEventsMap } from "@socket.io/component-emitter";
|
||||
import type { ComputedNodeInfo } from "./useOverviewInfo";
|
||||
import { ref } from "vue";
|
||||
import { removeTrail } from "@/tools/string";
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
export enum SocketStatus {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Connected = 1,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Connecting = 2,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Error = 0
|
||||
}
|
||||
|
||||
export function useSocketIoClient() {
|
||||
let socket: Socket<DefaultEventsMap, DefaultEventsMap> | undefined;
|
||||
const socketStatus = ref<SocketStatus>(SocketStatus.Connecting);
|
||||
const parseIp = (ip: string) => {
|
||||
if (ip.toLowerCase() === "localhost" || ip === "127.0.0.1") {
|
||||
return window.location.hostname;
|
||||
}
|
||||
return ip;
|
||||
};
|
||||
|
||||
const testFrontendSocket = async (remoteNode?: Partial<ComputedNodeInfo>) => {
|
||||
const nodeCfg = remoteNode;
|
||||
|
||||
if (!nodeCfg?.available || !nodeCfg.ip) {
|
||||
socketStatus.value = SocketStatus.Error;
|
||||
} else {
|
||||
try {
|
||||
socketStatus.value = SocketStatus.Connecting;
|
||||
await testConnect(
|
||||
parseIp(nodeCfg.ip) + ":" + nodeCfg.port,
|
||||
removeTrail(nodeCfg.prefix || "", "/") + "/socket.io"
|
||||
);
|
||||
socketStatus.value = SocketStatus.Connected;
|
||||
} catch (error) {
|
||||
console.error("Socket error: ", error);
|
||||
socketStatus.value = SocketStatus.Error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const testConnect = (addr: string, path: string) => {
|
||||
socket = io(addr, {
|
||||
@ -32,5 +72,5 @@ export function useSocketIoClient() {
|
||||
});
|
||||
};
|
||||
|
||||
return { testConnect };
|
||||
return { testConnect, parseIp, testFrontendSocket, socketStatus };
|
||||
}
|
||||
|
10
frontend/src/tools/version.ts
Normal file
10
frontend/src/tools/version.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export function hasVersionUpdate(oldVersion?: string, newVersion?: string) {
|
||||
if (!oldVersion || !newVersion) return true;
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [oldMajor, oldMinor, _oldPatch] = oldVersion.split(".");
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [newMajor, newMinor, _newPatch] = newVersion.split(".");
|
||||
|
||||
return oldMajor !== newMajor || oldMinor !== newMinor;
|
||||
}
|
@ -500,7 +500,7 @@ onMounted(async () => {
|
||||
:target-instance-info="item"
|
||||
:target-daemon-id="currentRemoteNode?.uuid"
|
||||
@click="handleSelectInstance(item)"
|
||||
@refrsh-list="initInstancesData()"
|
||||
@refresh-list="initInstancesData()"
|
||||
/>
|
||||
</a-col>
|
||||
</fade-up-animation>
|
||||
|
@ -99,10 +99,16 @@ onMounted(async () => {
|
||||
</a-typography-paragraph>
|
||||
|
||||
<a-typography-paragraph v-if="instanceGameServerInfo">
|
||||
{{ t("TXT_CODE_855c4a1c") }}{{ instanceGameServerInfo.players }}
|
||||
<span>{{ t("TXT_CODE_855c4a1c") }}</span>
|
||||
<span>{{ instanceGameServerInfo.players }}</span>
|
||||
</a-typography-paragraph>
|
||||
<a-typography-paragraph v-if="instanceGameServerInfo">
|
||||
{{ t("TXT_CODE_e260a220") }}{{ instanceGameServerInfo.version }}
|
||||
<span>
|
||||
{{ t("TXT_CODE_e260a220") }}
|
||||
</span>
|
||||
<span>
|
||||
{{ instanceGameServerInfo.version }}
|
||||
</span>
|
||||
</a-typography-paragraph>
|
||||
|
||||
<a-typography-paragraph>
|
||||
|
@ -70,6 +70,7 @@ const {
|
||||
beforeUpload,
|
||||
downloadFile,
|
||||
handleChangeDir,
|
||||
handleSearchChange,
|
||||
selectedFile,
|
||||
rowClickTable,
|
||||
handleTableChange,
|
||||
@ -383,7 +384,7 @@ onUnmounted(() => {
|
||||
v-model:value.trim.lazy="operationForm.name"
|
||||
:placeholder="t('TXT_CODE_7cad42a5')"
|
||||
allow-clear
|
||||
@change="getFileList()"
|
||||
@change="handleSearchChange()"
|
||||
>
|
||||
<template #suffix>
|
||||
<search-outlined />
|
||||
|
@ -32,6 +32,7 @@ import { arrayFilter } from "@/tools/array";
|
||||
import { useLayoutContainerStore } from "@/stores/useLayoutContainerStore";
|
||||
import { reportErrorMsg } from "@/tools/validator";
|
||||
import { openInstanceTagsEditor } from "@/components/fc/index";
|
||||
import _ from "lodash";
|
||||
|
||||
const props = defineProps<{
|
||||
card: LayoutCard;
|
||||
@ -39,7 +40,7 @@ const props = defineProps<{
|
||||
targetDaemonId?: string;
|
||||
}>();
|
||||
|
||||
const emits = defineEmits(["refrshList"]);
|
||||
const emits = defineEmits(["refreshList"]);
|
||||
|
||||
const { containerState } = useLayoutContainerStore();
|
||||
const { getMetaOrRouteValue } = useLayoutCardTools(props.card);
|
||||
@ -69,7 +70,7 @@ const { isLoading: updateLoading, execute: executeUpdate } = updateInstance();
|
||||
|
||||
const refreshList = () => {
|
||||
setTimeout(() => {
|
||||
emits("refrshList");
|
||||
emits("refreshList");
|
||||
}, 500);
|
||||
};
|
||||
|
||||
@ -194,7 +195,8 @@ const instanceOperations = computed(() =>
|
||||
},
|
||||
loading: killLoading.value,
|
||||
disabled: containerState.isDesignMode,
|
||||
danger: true
|
||||
danger: true,
|
||||
condition: () => !isStopped.value
|
||||
},
|
||||
{
|
||||
area: true
|
||||
@ -206,8 +208,8 @@ const instanceOperations = computed(() =>
|
||||
event.stopPropagation();
|
||||
if (instanceId && daemonId) {
|
||||
const tags = instanceInfo.value?.config.tag || [];
|
||||
await openInstanceTagsEditor(instanceId, daemonId, tags);
|
||||
refreshList();
|
||||
const newTags = await openInstanceTagsEditor(instanceId, daemonId, tags);
|
||||
if (!_.isEqual(newTags, tags)) refreshList();
|
||||
}
|
||||
},
|
||||
disabled: containerState.isDesignMode
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, unref } from "vue";
|
||||
import { t } from "@/lang/i18n";
|
||||
import { $t, t } from "@/lang/i18n";
|
||||
import { useScreen } from "@/hooks/useScreen";
|
||||
import type { InstanceDetail, DockerNetworkModes } from "@/types";
|
||||
import type { FormInstance } from "ant-design-vue";
|
||||
@ -39,8 +39,11 @@ const props = defineProps<{
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
enum TabSettings {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Basic = "Basic",
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Docker = "Docker",
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Advanced = "Advanced"
|
||||
}
|
||||
|
||||
@ -64,6 +67,7 @@ const IMAGE_DEFINE = {
|
||||
EDIT: "__MCSM_EDIT_IMAGE__"
|
||||
};
|
||||
|
||||
const updateCommandDesc = $t("TXT_CODE_fa487a47");
|
||||
const UPDATE_CMD_TEMPLATE =
|
||||
t("TXT_CODE_61ca492b") +
|
||||
`"C:/SteamCMD/steamcmd.exe" +login anonymous +force_install_dir "{mcsm_workspace}" "+app_update 380870 validate" +quit`;
|
||||
@ -160,7 +164,6 @@ const rules: Record<string, any> = {
|
||||
{
|
||||
required: true,
|
||||
validator: async (_rule: Rule, value: string) => {
|
||||
if (value === "") throw new Error(t("TXT_CODE_4e810102"));
|
||||
if (value.includes("\n")) throw new Error(t("TXT_CODE_bbbda29"));
|
||||
},
|
||||
trigger: "change"
|
||||
@ -405,10 +408,10 @@ defineExpose({
|
||||
<a-typography-title :level="5">{{ t("TXT_CODE_bb0b9711") }}</a-typography-title>
|
||||
<a-typography-paragraph>
|
||||
<a-typography-text type="secondary">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-html="t('TXT_CODE_41763172')"></span>
|
||||
<br />
|
||||
<span>{{ t("TXT_CODE_4f387c5a") }}</span>
|
||||
<br />
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-html="updateCommandDesc"> </span>
|
||||
</a-typography-text>
|
||||
</a-typography-paragraph>
|
||||
<!-- eslint-disable-next-line vue/html-quotes -->
|
||||
|
@ -25,20 +25,10 @@ import type { LayoutCard } from "@/types";
|
||||
import { arrayFilter } from "@/tools/array";
|
||||
import { GLOBAL_INSTANCE_UUID } from "@/config/const";
|
||||
import NodeDetailDialog from "./NodeDetailDialog.vue";
|
||||
import { useSocketIoClient } from "@/hooks/useSocketIo";
|
||||
import { removeTrail } from "@/tools/string";
|
||||
import { SocketStatus, useSocketIoClient } from "@/hooks/useSocketIo";
|
||||
import { hasVersionUpdate } from "@/tools/version";
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
enum SocketStatus {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Connected = 1,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Connecting = 2,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Error = 0
|
||||
}
|
||||
|
||||
const { testConnect } = useSocketIoClient();
|
||||
const { testFrontendSocket, socketStatus } = useSocketIoClient();
|
||||
|
||||
const nodeDetailDialog = ref<InstanceType<typeof NodeDetailDialog>>();
|
||||
|
||||
@ -48,8 +38,8 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const { state: AllDaemonData } = useOverviewInfo();
|
||||
|
||||
const itemDaemonId = ref<string>();
|
||||
const socketStatus = ref<SocketStatus>(SocketStatus.Connecting);
|
||||
const specifiedDaemonVersion = computed(() => AllDaemonData.value?.specifiedDaemonVersion);
|
||||
|
||||
const remoteNode = computed(() => {
|
||||
@ -123,8 +113,8 @@ const detailList = (node: ComputedNodeInfo) => [
|
||||
{
|
||||
title: t("TXT_CODE_81634069"),
|
||||
value: node.version,
|
||||
success: specifiedDaemonVersion.value === node.version,
|
||||
warn: specifiedDaemonVersion.value !== node.version && node.available,
|
||||
success: !hasVersionUpdate(specifiedDaemonVersion.value, node.version),
|
||||
warn: hasVersionUpdate(specifiedDaemonVersion.value, node.version) && node.available,
|
||||
warnText: t("TXT_CODE_e520908a")
|
||||
},
|
||||
{
|
||||
@ -200,27 +190,8 @@ const nodeOperations = computed(() =>
|
||||
])
|
||||
);
|
||||
|
||||
const testFrontendSocket = async () => {
|
||||
const nodeCfg = remoteNode.value;
|
||||
if (!nodeCfg?.available) {
|
||||
socketStatus.value = SocketStatus.Error;
|
||||
} else {
|
||||
try {
|
||||
socketStatus.value = SocketStatus.Connecting;
|
||||
await testConnect(
|
||||
nodeCfg.ip + ":" + nodeCfg.port,
|
||||
removeTrail(nodeCfg.prefix, "/") + "/socket.io"
|
||||
);
|
||||
socketStatus.value = SocketStatus.Connected;
|
||||
} catch (error) {
|
||||
console.error("Socket error: ", error);
|
||||
socketStatus.value = SocketStatus.Error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
testFrontendSocket();
|
||||
testFrontendSocket(remoteNode.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -364,7 +364,6 @@
|
||||
"TXT_CODE_40ee4eaf": "Parallelität erzeugen",
|
||||
"TXT_CODE_413b9c01": "Knotenumgebungsversion",
|
||||
"TXT_CODE_41406a5f": "Datenkarte",
|
||||
"TXT_CODE_41763172": "Dieser Systembefehl wird ausgeführt, wenn der Benutzer einen Update-/Installationsvorgang durchführt.",
|
||||
"TXT_CODE_41d79430": "Geeignet für die Serverbetriebsumgebung MC Bedrock Edition oder andere Linux-Programme",
|
||||
"TXT_CODE_41dd4d19": "Offizielle Website",
|
||||
"TXT_CODE_42036f92": "------Benutzeranmeldeereignis ------",
|
||||
@ -1917,7 +1916,7 @@
|
||||
"TXT_CODE_40241d8e": "Erwerb des Minecraft-Status",
|
||||
"TXT_CODE_57d1929e": "MCSManager verwendet das Minecraft-Ping-Protokoll, um die Anzahl der Personen, die auf dem Server online sind, die Version, die Latenz und andere Informationen zu ermitteln.",
|
||||
"TXT_CODE_6b175558": "Nach Abschluss der Konfiguration wird der Serverstatus jede Minute aktualisiert, wenn die Instanz ausgeführt wird.",
|
||||
"TXT_CODE_e260a220": "Spielversion:",
|
||||
"TXT_CODE_e260a220": "Spielversion: ",
|
||||
"TXT_CODE_13411df7": "Die SSO-Anmeldung ist fehlgeschlagen. Bitte kehren Sie zur ursprünglichen Seite zurück und versuchen Sie erneut, sich anzumelden!",
|
||||
"TXT_CODE_2082f659": "Hinweis: Ist dieser leer, werden die Dateien in der Dateiverwaltung nicht an den Container gebunden!",
|
||||
"TXT_CODE_2c1337d": "Liste der Tags für diese Instanz",
|
||||
@ -1931,7 +1930,7 @@
|
||||
"TXT_CODE_6da85509": "Sind Sie sicher, dass Sie der Instanz einen Befehl zum Herunterfahren erteilen möchten?",
|
||||
"TXT_CODE_7333c7f7": "Klar",
|
||||
"TXT_CODE_78e88c3f": "Etikettengruppierung",
|
||||
"TXT_CODE_855c4a1c": "Anzahl Spieler:",
|
||||
"TXT_CODE_855c4a1c": "Anzahl Spieler: ",
|
||||
"TXT_CODE_893567ac": "Zweite Bestätigung",
|
||||
"TXT_CODE_a2544278": "Instanzbezeichnung",
|
||||
"TXT_CODE_a8b0dfab": "Führen Sie eine 2x1-Berechnung für diesen Wert durch, um den Schutzradius des Spawnpunkts zu bestimmen. Wenn Sie ihn auf 0 setzen, wird nur der Block unterhalb des Spawnpunkts geschützt. \nAb Version 1.5 deaktiviert die Einstellung auf 0 oder einen negativen Wert den Build-Schutz.",
|
||||
@ -1948,5 +1947,7 @@
|
||||
"TXT_CODE_2fb14927": "Dieser Fehler bedeutet normalerweise, dass die Webseite keine direkte Verbindung zum Backend-Prozess herstellen kann. \nWenn Sie Portweiterleitung, Mapping, Reverse-Proxy haben, überprüfen Sie bitte die Konfiguration!",
|
||||
"TXT_CODE_46c48969": "Anscheinend versuchen Sie, HTTPS zu verwenden, aber wir können über das WSS-Protokoll keine Verbindung zum Daemon herstellen. \nBitte versuchen Sie, über das WSS://-Protokoll eine Verbindung zum Daemon herzustellen.",
|
||||
"TXT_CODE_9b3ce825": "Beschreibung des Reverse-Proxys",
|
||||
"TXT_CODE_d4c8fb3b": "Zugehörige Dokumente:"
|
||||
"TXT_CODE_d4c8fb3b": "Zugehörige Dokumente: ",
|
||||
"TXT_CODE_a6424dcc": "Das Arbeitsverzeichnis oder der Startbefehl darf nicht leer sein",
|
||||
"TXT_CODE_ffa884f9": "Warnung: Das Arbeitsverzeichnis ist nicht im Container gemountet und der Container kann nicht auf Dateien im Arbeitsverzeichnis der Instanz zugreifen"
|
||||
}
|
||||
|
@ -380,7 +380,7 @@
|
||||
"TXT_CODE_4ab6a0b5": "Browser Time",
|
||||
"TXT_CODE_856bd2f3": "Temporary Banned IPs",
|
||||
"TXT_CODE_da8f97a7": "Blocked Accesses",
|
||||
"TXT_CODE_190ecd56": "Linux Load",
|
||||
"TXT_CODE_190ecd56": "Unix Load Average",
|
||||
"TXT_CODE_77d038f7": "Panel Memory Usage",
|
||||
"TXT_CODE_4df7e9bd": "Hostname",
|
||||
"TXT_CODE_b4d8588": "OS Version",
|
||||
@ -1197,7 +1197,6 @@
|
||||
"TXT_CODE_ee67e1a3": "Working Directory",
|
||||
"TXT_CODE_962d9320": "The working directory where the instance runs. You can provide either an absolute or relative path.",
|
||||
"TXT_CODE_bb0b9711": "Update or Install Command",
|
||||
"TXT_CODE_41763172": "This system command will be executed when the user performs an update/installation operation, {mcsm_workspace} represents the working directory.",
|
||||
"TXT_CODE_f041de90": "File Encoding",
|
||||
"TXT_CODE_6e69b5a5": "For file management functions.",
|
||||
"TXT_CODE_fa920c0": "Expiration Time",
|
||||
@ -1723,7 +1722,7 @@
|
||||
"TXT_CODE_434786c9": "After uploading a background image, the panel will switch to dark theme with a blurred translucent effect, you can switch back anytime.",
|
||||
"TXT_CODE_7ceebc05": "Enter text content, support Markdown syntax, and can wrap lines.\n\nDon’t use other people’s copywriting easily, otherwise malicious code may be injected to attack you.",
|
||||
"TXT_CODE_4b6e951": "Input",
|
||||
"TXT_CODE_61ca492b": "For Example:",
|
||||
"TXT_CODE_61ca492b": "For Example: ",
|
||||
"TXT_CODE_ae09d79d": "Upload",
|
||||
"TXT_CODE_bb888626": "Output",
|
||||
"TXT_CODE_1b1b2934": "Failed to send RCON command due to incorrect password, please check the access password set in the Steam Rcon protocol!",
|
||||
@ -1736,8 +1735,8 @@
|
||||
"TXT_CODE_cc561947": "Scan the QR code using Google Authentication or other universal 2FA tools.",
|
||||
"TXT_CODE_af2a6972": "Click the 'I have scanned' button.",
|
||||
"TXT_CODE_b0a18c20": "I have scanned",
|
||||
"TXT_CODE_30051f9b": "Instance ID:",
|
||||
"TXT_CODE_5f2d2e30": "Node ID:",
|
||||
"TXT_CODE_30051f9b": "Instance ID: ",
|
||||
"TXT_CODE_5f2d2e30": "Node ID: ",
|
||||
"TXT_CODE_8f47d95": "Saved via shortcut keys!",
|
||||
"TXT_CODE_282b0721": "Steam Rcon Protocol Settings",
|
||||
"TXT_CODE_32d87bf1": "Most Steam game servers require RCON protocol to support command execution, including Minecraft game servers. If you find that the built-in console cannot execute commands when running a Steam game server, try configuring RCON protocol to support command execution.",
|
||||
@ -1911,14 +1910,14 @@
|
||||
"TXT_CODE_4aaec75c": "Wrong request type, please try again!",
|
||||
"TXT_CODE_903b6c50": "User does not exist, please try again",
|
||||
"TXT_CODE_7e9727bd": "Players",
|
||||
"TXT_CODE_855c4a1c": "Players:",
|
||||
"TXT_CODE_e260a220": "Version:",
|
||||
"TXT_CODE_855c4a1c": "Players: ",
|
||||
"TXT_CODE_e260a220": "Version: ",
|
||||
"TXT_CODE_40241d8e": "Minecraft Players Query",
|
||||
"TXT_CODE_57d1929e": "MCSManager will use the \"Minecraft Ping protocol\" to try to obtain the number of online servers, versions, delays and other information.",
|
||||
"TXT_CODE_6b175558": "After completing the configuration, the server status will be refreshed every minute when the instance is running.",
|
||||
"TXT_CODE_2f59807a": "Server address",
|
||||
"TXT_CODE_ddc2de99": "Default: localhost",
|
||||
"TXT_CODE_33a09033": "Players:",
|
||||
"TXT_CODE_33a09033": "Players: ",
|
||||
"TXT_CODE_ba717ff3": "You can freely design the layout, order and display of most elements on the web page.",
|
||||
"TXT_CODE_bc46c15b": "Start Designing",
|
||||
"TXT_CODE_3b24a247": "Click the Save button in the upper right corner to save the layout design.",
|
||||
@ -1948,5 +1947,13 @@
|
||||
"TXT_CODE_9b3ce825": "Reverse proxy description",
|
||||
"TXT_CODE_10cc2794": "Network architecture description",
|
||||
"TXT_CODE_46c48969": "It looks like you are trying to use HTTPS, but we cannot connect to the daemon using the WSS protocol. Please try to connect to the daemon using the WSS:// protocol.",
|
||||
"TXT_CODE_2fb14927": "This error usually means that the web page cannot connect directly to the backend process. If you have port forwarding, mapping, reverse proxy, please check the configuration!"
|
||||
"TXT_CODE_2fb14927": "This error usually means that the web page cannot connect directly to the backend process. If you have port forwarding, mapping, reverse proxy, please check the configuration!",
|
||||
"TXT_CODE_a6424dcc": "The working directory or startup command cannot be empty",
|
||||
"TXT_CODE_ffa884f9": "Warning: The working directory is not mounted to the container, and the container will not be able to access files in the instance's working directory",
|
||||
"TXT_CODE_6c232c9c": "Used to mount additional folders on the host into the container, supports two variable strings: {mcsm_workspace} (working directory) and {mcsm_instance_id} (instance ID).",
|
||||
"TXT_CODE_fa487a47": "Provide the following variable strings: {mcsm_instance_id} = instance ID, {mcsm_workspace} = instance installation directory",
|
||||
"TXT_CODE_6259357c": "The program must run for more than 6 seconds before it can be forced to stop!",
|
||||
"TXT_CODE_9d1d244f": "Program startup failed, input and output streams are unreadable: {{pipeName}}",
|
||||
"TXT_CODE_ca030197": "The instance has other tasks in progress, and this operation cannot be performed!",
|
||||
"TXT_CODE_aae2918f": "The instance process failed to start, please check the startup command and operating environment configuration!"
|
||||
}
|
||||
|
@ -364,7 +364,6 @@
|
||||
"TXT_CODE_40ee4eaf": "producir paralelismo",
|
||||
"TXT_CODE_413b9c01": "Versión del entorno de nodo",
|
||||
"TXT_CODE_41406a5f": "tarjeta de datos",
|
||||
"TXT_CODE_41763172": "Este comando del sistema se ejecutará cuando el usuario realice una operación de actualización/instalación.",
|
||||
"TXT_CODE_41d79430": "Adecuado para el entorno operativo del servidor MC Bedrock Edition u otros programas Linux",
|
||||
"TXT_CODE_41dd4d19": "Página web oficial",
|
||||
"TXT_CODE_42036f92": "------Evento de inicio de sesión de usuario ------",
|
||||
@ -1917,7 +1916,7 @@
|
||||
"TXT_CODE_40241d8e": "Adquisición de estado de Minecraft",
|
||||
"TXT_CODE_57d1929e": "MCSManager utilizará el protocolo Minecraft Ping para intentar obtener la cantidad de personas en línea en el servidor, la versión, la latencia y otra información.",
|
||||
"TXT_CODE_6b175558": "Después de completar la configuración, cuando la instancia se está ejecutando, el estado del servidor se actualiza cada minuto.",
|
||||
"TXT_CODE_e260a220": "Versión del juego:",
|
||||
"TXT_CODE_e260a220": "Versión del juego: ",
|
||||
"TXT_CODE_13411df7": "Error al iniciar sesión en SSO. Vuelva a la página original e intente iniciar sesión nuevamente.",
|
||||
"TXT_CODE_2082f659": "Nota: Si está vacío, los archivos en la administración de archivos no estarán vinculados al contenedor.",
|
||||
"TXT_CODE_2c1337d": "Lista de etiquetas para esta instancia",
|
||||
@ -1931,7 +1930,7 @@
|
||||
"TXT_CODE_6da85509": "¿Está seguro de que desea emitir un comando de apagado para la instancia?",
|
||||
"TXT_CODE_7333c7f7": "Claro",
|
||||
"TXT_CODE_78e88c3f": "Agrupación de etiquetas",
|
||||
"TXT_CODE_855c4a1c": "Número de jugadores:",
|
||||
"TXT_CODE_855c4a1c": "Número de jugadores: ",
|
||||
"TXT_CODE_893567ac": "Segunda confirmación",
|
||||
"TXT_CODE_a2544278": "etiqueta de instancia",
|
||||
"TXT_CODE_a8b0dfab": "Realice un cálculo de 2x 1 en este valor para determinar el radio de protección del punto de generación. Establecerlo en 0 solo protegerá el bloque debajo del punto de generación. \nA partir de la versión 1.5, establecer en 0 o un valor negativo deshabilita la protección de compilación.",
|
||||
@ -1948,5 +1947,7 @@
|
||||
"TXT_CODE_2fb14927": "Este error generalmente significa que la página web no puede conectarse directamente al proceso backend. \nSi tiene reenvío de puertos, mapeo o proxy inverso, verifique la configuración.",
|
||||
"TXT_CODE_46c48969": "Parece que está intentando utilizar HTTPS, pero no podemos conectarnos al demonio mediante el protocolo WSS. \nIntente conectarse al demonio utilizando el protocolo WSS://.",
|
||||
"TXT_CODE_9b3ce825": "Descripción del proxy inverso",
|
||||
"TXT_CODE_d4c8fb3b": "Documentos relacionados:"
|
||||
"TXT_CODE_d4c8fb3b": "Documentos relacionados: ",
|
||||
"TXT_CODE_a6424dcc": "El directorio de trabajo o el comando de inicio no pueden estar vacíos",
|
||||
"TXT_CODE_ffa884f9": "Advertencia: el directorio de trabajo no está montado en el contenedor y el contenedor no podrá acceder a los archivos en el directorio de trabajo de la instancia."
|
||||
}
|
||||
|
@ -364,7 +364,6 @@
|
||||
"TXT_CODE_40ee4eaf": "produire du parallélisme",
|
||||
"TXT_CODE_413b9c01": "Version de l'environnement du nœud",
|
||||
"TXT_CODE_41406a5f": "carte de données",
|
||||
"TXT_CODE_41763172": "Cette commande système sera exécutée lorsque l'utilisateur effectuera une opération de mise à jour/installation.",
|
||||
"TXT_CODE_41d79430": "Convient à l'environnement d'exploitation du serveur MC Bedrock Edition ou à d'autres programmes Linux",
|
||||
"TXT_CODE_41dd4d19": "Site officiel",
|
||||
"TXT_CODE_42036f92": "------Événement de connexion utilisateur ------",
|
||||
@ -1931,7 +1930,7 @@
|
||||
"TXT_CODE_6da85509": "Êtes-vous sûr de vouloir émettre une commande d'arrêt sur l'instance ?",
|
||||
"TXT_CODE_7333c7f7": "Clair",
|
||||
"TXT_CODE_78e88c3f": "Regroupement d'étiquettes",
|
||||
"TXT_CODE_855c4a1c": "Nombre de joueurs :",
|
||||
"TXT_CODE_855c4a1c": "Nombre de joueurs: ",
|
||||
"TXT_CODE_893567ac": "Deuxième confirmation",
|
||||
"TXT_CODE_a2544278": "Étiquette d'instance",
|
||||
"TXT_CODE_a8b0dfab": "Effectuez un calcul 2x 1 sur cette valeur pour déterminer le rayon de protection du point d'apparition. Le régler sur 0 ne protégera que le bloc situé en dessous du point d'apparition. \nÀ partir de la version 1.5, la définition sur 0 ou sur une valeur négative désactive la protection de build.",
|
||||
@ -1948,5 +1947,7 @@
|
||||
"TXT_CODE_2fb14927": "Cette erreur signifie généralement que la page Web ne peut pas se connecter directement au processus backend. \nSi vous disposez d'une redirection de port, d'un mappage, d'un proxy inverse, veuillez vérifier la configuration !",
|
||||
"TXT_CODE_46c48969": "Il semble que vous essayez d'utiliser HTTPS, mais nous ne parvenons pas à nous connecter au démon à l'aide du protocole WSS. \nVeuillez essayer de vous connecter au démon en utilisant le protocole WSS://.",
|
||||
"TXT_CODE_9b3ce825": "Description du proxy inverse",
|
||||
"TXT_CODE_d4c8fb3b": "Documents associés :"
|
||||
"TXT_CODE_d4c8fb3b": "Documents associés :",
|
||||
"TXT_CODE_a6424dcc": "Le répertoire de travail ou la commande de démarrage ne peut pas être vide",
|
||||
"TXT_CODE_ffa884f9": "Avertissement : Le répertoire de travail n'est pas monté sur le conteneur et le conteneur ne pourra pas accéder aux fichiers du répertoire de travail de l'instance."
|
||||
}
|
||||
|
@ -364,7 +364,6 @@
|
||||
"TXT_CODE_40ee4eaf": "並列性を生み出す",
|
||||
"TXT_CODE_413b9c01": "ノード環境のバージョン",
|
||||
"TXT_CODE_41406a5f": "データカード",
|
||||
"TXT_CODE_41763172": "このシステム コマンドは、ユーザーがアップデート/インストール操作を実行するときに実行されます。",
|
||||
"TXT_CODE_41d79430": "MC Bedrock Edition サーバーのオペレーティング環境またはその他の Linux プログラムに適しています",
|
||||
"TXT_CODE_41dd4d19": "公式ウェブサイト",
|
||||
"TXT_CODE_42036f92": "------ユーザーログインイベント ------",
|
||||
@ -1948,5 +1947,7 @@
|
||||
"TXT_CODE_2fb14927": "このエラーは通常、Web ページがバックエンド プロセスに直接接続できないことを意味します。\nポートフォワーディング、マッピング、リバースプロキシを使用している場合は、設定を確認してください。",
|
||||
"TXT_CODE_46c48969": "HTTPS を使用しようとしているようですが、WSS プロトコルを使用してデーモンに接続できません。 \nWSS:// プロトコルを使用してデーモンへの接続を試行してください。",
|
||||
"TXT_CODE_9b3ce825": "リバースプロキシの説明",
|
||||
"TXT_CODE_d4c8fb3b": "関連文書:"
|
||||
"TXT_CODE_d4c8fb3b": "関連文書:",
|
||||
"TXT_CODE_a6424dcc": "作業ディレクトリまたは起動コマンドを空にすることはできません",
|
||||
"TXT_CODE_ffa884f9": "警告: 作業ディレクトリがコンテナにマウントされていないため、コンテナはインスタンスの作業ディレクトリ内のファイルにアクセスできません"
|
||||
}
|
||||
|
@ -364,7 +364,6 @@
|
||||
"TXT_CODE_40ee4eaf": "병렬성을 생성",
|
||||
"TXT_CODE_413b9c01": "노드 환경 버전",
|
||||
"TXT_CODE_41406a5f": "데이터 카드",
|
||||
"TXT_CODE_41763172": "이 시스템 명령은 사용자가 업데이트/설치 작업을 수행할 때 실행됩니다.",
|
||||
"TXT_CODE_41d79430": "MC Bedrock Edition 서버 운영 환경 또는 기타 Linux 프로그램에 적합",
|
||||
"TXT_CODE_41dd4d19": "공식 웹 사이트",
|
||||
"TXT_CODE_42036f92": "------사용자 로그인 이벤트 ------",
|
||||
@ -1948,5 +1947,7 @@
|
||||
"TXT_CODE_2fb14927": "이 오류는 일반적으로 웹페이지가 백엔드 프로세스에 직접 연결할 수 없음을 의미합니다. \n포트포워딩, 매핑, 리버스프록시가 있는 경우 구성을 확인해주세요!",
|
||||
"TXT_CODE_46c48969": "HTTPS를 사용하려는 것 같지만 WSS 프로토콜을 사용하여 데몬에 연결할 수 없습니다. \nWSS:// 프로토콜을 사용하여 데몬에 연결해 보십시오.",
|
||||
"TXT_CODE_9b3ce825": "역방향 프록시 설명",
|
||||
"TXT_CODE_d4c8fb3b": "관련 문서:"
|
||||
"TXT_CODE_d4c8fb3b": "관련 문서:",
|
||||
"TXT_CODE_a6424dcc": "작업 디렉터리 또는 시작 명령은 비워둘 수 없습니다.",
|
||||
"TXT_CODE_ffa884f9": "경고: 작업 디렉터리가 컨테이너에 마운트되지 않았으므로 컨테이너는 인스턴스의 작업 디렉터리에 있는 파일에 액세스할 수 없습니다."
|
||||
}
|
||||
|
@ -1195,7 +1195,6 @@
|
||||
"TXT_CODE_ee67e1a3": "Diretório de Trabalho",
|
||||
"TXT_CODE_962d9320": "O diretório de trabalho onde a instância é executada. Você pode fornecer um caminho absoluto ou relativo.",
|
||||
"TXT_CODE_bb0b9711": "Comando de Atualização ou Instalação",
|
||||
"TXT_CODE_41763172": "Este comando do sistema será executado quando o usuário realizar uma operação de atualização/instalação, {mcsm_workspace} representa o diretório de trabalho.",
|
||||
"TXT_CODE_f041de90": "Codificação de Arquivo",
|
||||
"TXT_CODE_6e69b5a5": "Para funções de gerenciamento de arquivos.",
|
||||
"TXT_CODE_fa920c0": "Tempo de Expiração",
|
||||
@ -1948,5 +1947,7 @@
|
||||
"TXT_CODE_2fb14927": "Este erro geralmente significa que a página da web não pode se conectar diretamente ao processo de back-end. \nSe você possui encaminhamento de porta, mapeamento, proxy reverso, verifique a configuração!",
|
||||
"TXT_CODE_46c48969": "Parece que você está tentando usar HTTPS, mas não conseguimos nos conectar ao daemon usando o protocolo WSS. \nTente se conectar ao daemon usando o protocolo WSS://.",
|
||||
"TXT_CODE_9b3ce825": "Descrição do proxy reverso",
|
||||
"TXT_CODE_d4c8fb3b": "Documentos relacionados:"
|
||||
"TXT_CODE_d4c8fb3b": "Documentos relacionados:",
|
||||
"TXT_CODE_a6424dcc": "O diretório de trabalho ou comando de inicialização não pode estar vazio",
|
||||
"TXT_CODE_ffa884f9": "Aviso: o diretório de trabalho não está montado no contêiner e o contêiner não poderá acessar arquivos no diretório de trabalho da instância"
|
||||
}
|
||||
|
@ -364,7 +364,6 @@
|
||||
"TXT_CODE_40ee4eaf": "производить параллелизм",
|
||||
"TXT_CODE_413b9c01": "Версия среды узла",
|
||||
"TXT_CODE_41406a5f": "данные карты",
|
||||
"TXT_CODE_41763172": "Эта системная команда будет выполнена, когда пользователь выполнит операцию обновления/установки.",
|
||||
"TXT_CODE_41d79430": "Подходит для операционной среды сервера MC Bedrock Edition или других программ Linux.",
|
||||
"TXT_CODE_41dd4d19": "Официальный веб-сайт",
|
||||
"TXT_CODE_42036f92": "------Событие входа пользователя ------",
|
||||
@ -1948,5 +1947,7 @@
|
||||
"TXT_CODE_2fb14927": "Эта ошибка обычно означает, что веб-страница не может напрямую подключиться к серверному процессу. \nЕсли у вас есть переадресация портов, сопоставление, обратный прокси-сервер, проверьте конфигурацию!",
|
||||
"TXT_CODE_46c48969": "Похоже, вы пытаетесь использовать HTTPS, но мы не можем подключиться к демону по протоколу WSS. \nПопробуйте подключиться к демону по протоколу WSS://.",
|
||||
"TXT_CODE_9b3ce825": "Описание обратного прокси",
|
||||
"TXT_CODE_d4c8fb3b": "Сопутствующие документы:"
|
||||
"TXT_CODE_d4c8fb3b": "Сопутствующие документы:",
|
||||
"TXT_CODE_a6424dcc": "Рабочий каталог или команда запуска не могут быть пустыми.",
|
||||
"TXT_CODE_ffa884f9": "Предупреждение. Рабочий каталог не подключен к контейнеру, и контейнер не сможет получить доступ к файлам в рабочем каталоге экземпляра."
|
||||
}
|
||||
|
@ -1196,7 +1196,6 @@
|
||||
"TXT_CODE_ee67e1a3": "Çalışma Dizini",
|
||||
"TXT_CODE_962d9320": "Örneğin çalıştığı çalışma dizini. Mutlak veya göreli bir yol sağlayabilirsiniz.",
|
||||
"TXT_CODE_bb0b9711": "Güncelleme veya Yükleme Komutu",
|
||||
"TXT_CODE_41763172": "Bu sistem komutu, kullanıcı bir güncelleme/kurulum işlemi gerçekleştirdiğinde çalıştırılacaktır, {mcsm_workspace} çalışma dizinini temsil eder.",
|
||||
"TXT_CODE_f041de90": "Dosya Kodlaması",
|
||||
"TXT_CODE_6e69b5a5": "Dosya yönetimi işlevleri için.",
|
||||
"TXT_CODE_fa920c0": "Son Kullanma Süresi",
|
||||
@ -1948,5 +1947,7 @@
|
||||
"TXT_CODE_2fb14927": "Bu hata genellikle web sayfasının doğrudan arka uç işlemine bağlanamadığı anlamına gelir. \nBağlantı noktası yönlendirme, eşleme, ters proxy'niz varsa lütfen yapılandırmayı kontrol edin!",
|
||||
"TXT_CODE_46c48969": "Görünüşe göre HTTPS kullanmaya çalışıyorsunuz ancak arka plan programına WSS protokolünü kullanarak bağlanamıyoruz. \nLütfen WSS:// protokolünü kullanarak arka plan programına bağlanmayı deneyin.",
|
||||
"TXT_CODE_9b3ce825": "Ters proxy açıklaması",
|
||||
"TXT_CODE_d4c8fb3b": "İlgili belgeler:"
|
||||
"TXT_CODE_d4c8fb3b": "İlgili belgeler:",
|
||||
"TXT_CODE_a6424dcc": "Çalışma dizini veya başlatma komutu boş olamaz",
|
||||
"TXT_CODE_ffa884f9": "Uyarı: Çalışma dizini kapsayıcıya bağlanmamıştır ve kapsayıcı, örneğin çalışma dizinindeki dosyalara erişemeyecektir."
|
||||
}
|
||||
|
@ -290,7 +290,7 @@
|
||||
"TXT_CODE_d07742fe": "服务端配置文件",
|
||||
"TXT_CODE_1deaa2dd": "用户",
|
||||
"TXT_CODE_236f70aa": "用户配置",
|
||||
"TXT_CODE_342a04a9": "故障中",
|
||||
"TXT_CODE_342a04a9": "维护中",
|
||||
"TXT_CODE_15f2e564": "未运行",
|
||||
"TXT_CODE_a409b8a9": "停止中",
|
||||
"TXT_CODE_175b570d": "启动中",
|
||||
@ -380,7 +380,7 @@
|
||||
"TXT_CODE_4ab6a0b5": "浏览器时间",
|
||||
"TXT_CODE_856bd2f3": "临时封禁IP数",
|
||||
"TXT_CODE_da8f97a7": "已阻止的访问",
|
||||
"TXT_CODE_190ecd56": "Linux 负载",
|
||||
"TXT_CODE_190ecd56": "負載平均值",
|
||||
"TXT_CODE_77d038f7": "面板端使用内存量",
|
||||
"TXT_CODE_4df7e9bd": "主机名",
|
||||
"TXT_CODE_b4d8588": "系统版本",
|
||||
@ -1198,7 +1198,6 @@
|
||||
"TXT_CODE_ee67e1a3": "工作目录",
|
||||
"TXT_CODE_962d9320": "实例运行的工作目录,可填绝对路径与相对路径",
|
||||
"TXT_CODE_bb0b9711": "更新或安装命令",
|
||||
"TXT_CODE_41763172": "当用户执行更新/安装操作时,将会执行此系统命令,{mcsm_workspace} 代表工作目录",
|
||||
"TXT_CODE_f041de90": "文件管理编码",
|
||||
"TXT_CODE_6e69b5a5": "文件管理功能的解压缩,编辑等编码",
|
||||
"TXT_CODE_fa920c0": "到期时间",
|
||||
@ -1948,5 +1947,13 @@
|
||||
"TXT_CODE_9b3ce825": "反向代理说明",
|
||||
"TXT_CODE_10cc2794": "网络架构说明",
|
||||
"TXT_CODE_46c48969": "看起来您正在尝试使用 HTTPS,但是我们无法使用 WSS 协议连接到守护进程,请尝试使用 WSS:// 协议连接到守护进程。",
|
||||
"TXT_CODE_2fb14927": "此错误通常意味着网页无法直接连接到后端进程,如果您有端口转发,映射,反向代理,请检查配置!"
|
||||
"TXT_CODE_2fb14927": "此错误通常意味着网页无法直接连接到后端进程,如果您有端口转发,映射,反向代理,请检查配置!",
|
||||
"TXT_CODE_a6424dcc": "工作目录和启动命令不可为空",
|
||||
"TXT_CODE_ffa884f9": "警告:工作目录没有挂载到容器中,容器将无法访问到实例工作目录中的文件",
|
||||
"TXT_CODE_6c232c9c": "用于挂载主机上额外的文件夹到容器中,支持 {mcsm_workspace}(工作目录)和 {mcsm_instance_id}(实例ID)两个变量字符串。",
|
||||
"TXT_CODE_fa487a47": "提供以下变量字符串:{mcsm_instance_id} = 实例ID,{mcsm_workspace} = 实例安装目录",
|
||||
"TXT_CODE_6259357c": "程序必须运行超过6秒才能被强制停止!",
|
||||
"TXT_CODE_9d1d244f": "程序启动失败,输入输出流不可读:{{pipeName}}",
|
||||
"TXT_CODE_ca030197": "实例有其他任务正在进行中,无法进行此操作!",
|
||||
"TXT_CODE_aae2918f": "实例进程启动失败,请检查启动命令和运行环境等配置!"
|
||||
}
|
||||
|
@ -1198,7 +1198,6 @@
|
||||
"TXT_CODE_ee67e1a3": "工作目錄",
|
||||
"TXT_CODE_962d9320": "實例運行的工作目錄,可填入絕對路徑與相對路徑",
|
||||
"TXT_CODE_bb0b9711": "更新或安裝指令",
|
||||
"TXT_CODE_41763172": "當使用者執行更新/安裝作業時,將會執行此系統指令,{mcsm_workspace} 代表工作目錄",
|
||||
"TXT_CODE_f041de90": "檔案管理編碼",
|
||||
"TXT_CODE_6e69b5a5": "檔案管理功能的解壓縮,編輯等編碼",
|
||||
"TXT_CODE_fa920c0": "到期時間",
|
||||
@ -1779,7 +1778,7 @@
|
||||
"TXT_CODE_db37b7f9": "映像下載錯誤,請確保此映像名正確,或在節點管理的控制台處手動透過 docker pull 拉取您需要的映像,錯誤訊息:",
|
||||
"TXT_CODE_1cf6fc4b": "此容器的開放端口配置有誤!",
|
||||
"TXT_CODE_2029027e": "此容器的開放端口配置有誤,分隔符號左右兩邊不存在值!",
|
||||
"TXT_CODE_e76e49e9": "已掛載工作目錄:",
|
||||
"TXT_CODE_e76e49e9": "已載入工作目錄:",
|
||||
"TXT_CODE_90a9d317": "容器環境變數",
|
||||
"TXT_CODE_a42984e": "變數名稱",
|
||||
"TXT_CODE_115e8a25": "變數值",
|
||||
|
6
lib-urls.txt
Normal file
6
lib-urls.txt
Normal file
@ -0,0 +1,6 @@
|
||||
https://github.com/MCSManager/Zip-Tools/releases/download/latest/file_zip_linux_arm64
|
||||
https://github.com/MCSManager/Zip-Tools/releases/download/latest/file_zip_linux_x64
|
||||
https://github.com/MCSManager/Zip-Tools/releases/download/latest/file_zip_win32_x64.exe
|
||||
https://github.com/MCSManager/PTY/releases/download/latest/pty_linux_arm64
|
||||
https://github.com/MCSManager/PTY/releases/download/latest/pty_linux_x64
|
||||
https://github.com/MCSManager/PTY/releases/download/latest/pty_win32_x64.exe
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mcsmanager-panel",
|
||||
"version": "10.4.0",
|
||||
"version": "10.4.3",
|
||||
"daemonVersion": "4.5.0",
|
||||
"description": "Provide MCSManager with the ability to connect and control all Daemons, and provide API services for the front end.",
|
||||
"scripts": {
|
||||
|
@ -12,6 +12,7 @@ import { getUserUuid } from "../service/passport_service";
|
||||
import { isHaveInstanceByUuid, isTopPermissionByUuid } from "../service/permission_service";
|
||||
import { ROLE } from "../entity/user";
|
||||
import { removeTrail } from "common";
|
||||
import userSystem from "../service/user_service";
|
||||
|
||||
const router = new Router({ prefix: "/instance" });
|
||||
|
||||
@ -131,6 +132,12 @@ router.delete(
|
||||
const instanceUuids = ctx.request.body.uuids;
|
||||
const deleteFile = ctx.request.body.deleteFile;
|
||||
const remoteService = RemoteServiceSubsystem.getInstance(daemonId);
|
||||
if (!instanceUuids || !Array.isArray(instanceUuids))
|
||||
throw new Error("Type error, invalid uuids or daemonId");
|
||||
const instanceIds = instanceUuids.map((uuid: string) => {
|
||||
return { instanceUuid: uuid, daemonId };
|
||||
});
|
||||
userSystem.deleteUserInstances(null, instanceIds, true);
|
||||
const result = await new RemoteRequest(remoteService).request("instance/delete", {
|
||||
instanceUuids,
|
||||
deleteFile
|
||||
|
@ -108,6 +108,27 @@ class UserSubsystem {
|
||||
});
|
||||
});
|
||||
}
|
||||
deleteUserInstances(uuid: string | null, instanceIds: IUserApp[], allUsers = false) {
|
||||
if (uuid && allUsers) {
|
||||
throw new Error("Type error, The uuid and allUsers cannot be true at the same time.");
|
||||
}
|
||||
const users = allUsers ? Array.from(this.objects.values()) : [this.getInstance(uuid!)];
|
||||
if (!users || users.length === 0) return;
|
||||
instanceIds.forEach((value) => {
|
||||
if (!value.daemonId || !value.instanceUuid)
|
||||
throw new Error("Type error, The instances of user must be IUserHaveInstance array.");
|
||||
});
|
||||
users.forEach((user) => {
|
||||
if (!user) return;
|
||||
user.instances = user.instances.filter((value) => {
|
||||
for (const instance of instanceIds) {
|
||||
if (instance.daemonId === value.daemonId && instance.instanceUuid === value.instanceUuid)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getUserByUserName(userName: string) {
|
||||
for (const map of this.objects) {
|
||||
|
9
prod-scripts/linux/install.sh
Normal file
9
prod-scripts/linux/install.sh
Normal file
@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
BASE_PATH=$(pwd)
|
||||
|
||||
cd "${BASE_PATH}/daemon"
|
||||
npm install --production --no-fund --no-audit
|
||||
|
||||
cd "${BASE_PATH}/web"
|
||||
npm install --production --no-fund --no-audit
|
4
prod-scripts/linux/start-daemon.sh
Executable file
4
prod-scripts/linux/start-daemon.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd daemon || exit
|
||||
node --max-old-space-size=8192 --enable-source-maps app.js
|
4
prod-scripts/linux/start-web.sh
Executable file
4
prod-scripts/linux/start-web.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd web || exit
|
||||
node --max-old-space-size=8192 --enable-source-maps app.js
|
5
prod-scripts/windows/start.bat
Normal file
5
prod-scripts/windows/start.bat
Normal file
@ -0,0 +1,5 @@
|
||||
cd daemon
|
||||
start node_app.exe --enable-source-maps --max-old-space-size=8192 app.js
|
||||
ping localhost
|
||||
cd ../web
|
||||
start node_app.exe --enable-source-maps --max-old-space-size=8192 app.js --open
|
Loading…
Reference in New Issue
Block a user