Compare commits

...

4 Commits

Author SHA1 Message Date
MaeLucia 2195e44429 chore(deploy): 将部署脚本中的提示信息翻译为英文
更新部署脚本中的提示信息,将所有中文提示翻译为英文,方便国际化使用
2026-02-27 13:09:22 +08:00
MaeLucia 9a5a399818 docs(deploy): 更新默认密码并添加JWT_SECRET配置
- 将默认密码从'admin123'改为'1221xian'以提高安全性
- 在.env.example中添加JWT_SECRET配置说明
- 同步更新所有部署文档中的密码信息
2026-02-27 12:50:37 +08:00
MaeLucia 8008ec8b35 docs: 更新README中的默认密码和项目创建指南
更新默认密码为更安全的版本,并完善项目创建流程说明
添加项目路径配置说明,解释其格式和用途
2026-02-27 12:44:59 +08:00
MaeLucia 5e59f350b6 feat(项目): 添加项目路径支持并更新相关功能
添加项目路径配置功能,支持在创建项目时指定路径前缀
更新静态服务器和URL生成逻辑以支持路径前缀
在项目详情页显示项目路径信息
添加http-proxy-middleware依赖
清理测试数据文件
2026-02-27 12:31:28 +08:00
19 changed files with 363 additions and 264 deletions

View File

@ -8,3 +8,6 @@ BASE_DOMAIN=
# 项目端口范围 # 项目端口范围
PROJECT_PORT_START=9000 PROJECT_PORT_START=9000
PROJECT_PORT_END=9100 PROJECT_PORT_END=9100
# JWT密钥生产环境请修改为安全的随机字符串
JWT_SECRET=your-secure-secret-key-change-this

View File

@ -71,10 +71,16 @@ cd ..
cat > .env << EOF cat > .env << EOF
PORT=8888 PORT=8888
JWT_SECRET=your-secure-secret-key-change-this JWT_SECRET=your-secure-secret-key-change-this
HOST=your-server-ip BASE_DOMAIN=your-domain.com
PROJECT_PORT_START=9000
PROJECT_PORT_END=9100
EOF EOF
``` ```
默认登录账号:
- 用户名: admin
- 密码: 1221xian
### 4. 启动服务 ### 4. 启动服务
```bash ```bash

View File

@ -73,7 +73,7 @@ npm start
### 默认账号 ### 默认账号
- 用户名: `admin` - 用户名: `admin`
- 密码: `admin123` - 密码: `1221xian`
## 使用指南 ## 使用指南
@ -81,8 +81,18 @@ npm start
1. 点击右上角「新建项目」按钮 1. 点击右上角「新建项目」按钮
2. 填写项目名称和描述 2. 填写项目名称和描述
3. 拖拽或点击上传项目文件 3. (可选)填写项目路径,例如 `/demo/lot-demo`
4. 点击「创建项目」完成 4. 拖拽或点击上传项目文件
5. 点击「创建项目」完成
### 项目路径说明
项目路径用于自定义项目的访问URL
- 格式:以 `/` 开头的相对路径,如 `/demo/lot-demo`
- 访问URL格式`http://域名:端口/项目路径`
- 示例:项目路径 `/demo/lot-demo`,端口 `9000`,则访问地址为 `http://localhost:9000/demo/lot-demo`
- 用途:支持同一域名下部署多个项目,通过不同路径区分
### 部署项目 ### 部署项目

View File

@ -34,6 +34,18 @@
></textarea> ></textarea>
</div> </div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">项目路径</label>
<input
v-model="form.path"
type="text"
class="input"
placeholder="例如:/demo/lot-demo"
/>
<p class="text-xs text-gray-500 mt-1">"/"开头的相对路径用于生成完整访问URL</p>
<p v-if="form.path && pathPreview" class="text-xs text-primary-600 mt-1">预览: {{ pathPreview }}</p>
</div>
<div> <div>
<label class="block text-sm font-medium text-gray-700 mb-2">项目文件 *</label> <label class="block text-sm font-medium text-gray-700 mb-2">项目文件 *</label>
@ -137,7 +149,7 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive } from 'vue' import { ref, reactive, computed } from 'vue'
import { useProjectStore } from '../stores/project' import { useProjectStore } from '../stores/project'
const emit = defineEmits(['close', 'success']) const emit = defineEmits(['close', 'success'])
@ -152,9 +164,20 @@ const uploadProgress = ref(0)
const form = reactive({ const form = reactive({
name: '', name: '',
description: '', description: '',
path: '',
files: [] files: []
}) })
const pathPreview = computed(() => {
if (!form.path) return ''
let path = form.path.trim()
if (!path.startsWith('/')) {
path = '/' + path
}
const baseUrl = window.location.origin
return `${baseUrl}${path}`
})
const triggerFileInput = () => { const triggerFileInput = () => {
fileInput.value?.click() fileInput.value?.click()
} }
@ -188,6 +211,11 @@ const handleSubmit = async () => {
return return
} }
let projectPath = form.path.trim()
if (projectPath && !projectPath.startsWith('/')) {
projectPath = '/' + projectPath
}
loading.value = true loading.value = true
error.value = '' error.value = ''
uploadProgress.value = 0 uploadProgress.value = 0
@ -196,6 +224,7 @@ const handleSubmit = async () => {
const formData = new FormData() const formData = new FormData()
formData.append('name', form.name) formData.append('name', form.name)
formData.append('description', form.description) formData.append('description', form.description)
formData.append('path', projectPath)
form.files.forEach(file => { form.files.forEach(file => {
formData.append('files', file) formData.append('files', file)

View File

@ -150,6 +150,10 @@
<dt class="text-sm text-gray-500">更新时间</dt> <dt class="text-sm text-gray-500">更新时间</dt>
<dd class="text-sm font-medium text-gray-900 mt-1">{{ formatDateTime(project.updatedAt) }}</dd> <dd class="text-sm font-medium text-gray-900 mt-1">{{ formatDateTime(project.updatedAt) }}</dd>
</div> </div>
<div v-if="project.path">
<dt class="text-sm text-gray-500">项目路径</dt>
<dd class="text-sm font-medium text-gray-900 mt-1 font-mono">{{ project.path }}</dd>
</div>
<div v-if="project.lastDeployed"> <div v-if="project.lastDeployed">
<dt class="text-sm text-gray-500">最后部署</dt> <dt class="text-sm text-gray-500">最后部署</dt>
<dd class="text-sm font-medium text-gray-900 mt-1">{{ formatDateTime(project.lastDeployed) }}</dd> <dd class="text-sm font-medium text-gray-900 mt-1">{{ formatDateTime(project.lastDeployed) }}</dd>

View File

@ -1,52 +0,0 @@
[
{
"timestamp": "2026-02-23T04:58:11.547Z",
"message": "Extracted dist.zip",
"type": "success"
},
{
"timestamp": "2026-02-23T04:58:11.965Z",
"message": "Project \"loT-demo\" created with 4 file(s)",
"type": "success"
},
{
"timestamp": "2026-02-23T04:58:19.431Z",
"message": "Project started on port 9000",
"type": "success"
},
{
"timestamp": "2026-02-23T04:58:36.076Z",
"message": "Project stopped",
"type": "info"
},
{
"timestamp": "2026-02-23T05:04:00.097Z",
"message": "Project started on port 9001",
"type": "success"
},
{
"timestamp": "2026-02-23T05:04:56.824Z",
"message": "Project stopped",
"type": "info"
},
{
"timestamp": "2026-02-23T05:43:59.981Z",
"message": "Project started on port 9000",
"type": "success"
},
{
"timestamp": "2026-02-23T05:48:32.620Z",
"message": "Project stopped",
"type": "info"
},
{
"timestamp": "2026-02-23T05:48:33.171Z",
"message": "Project started on port 9000",
"type": "success"
},
{
"timestamp": "2026-02-23T05:57:37.014Z",
"message": "Project stopped",
"type": "info"
}
]

View File

@ -1,62 +0,0 @@
[
{
"timestamp": "2026-02-23T05:03:36.023Z",
"message": "Extracted dist.zip",
"type": "success"
},
{
"timestamp": "2026-02-23T05:03:36.120Z",
"message": "Project \"rushe-demo\" created with 3 file(s)",
"type": "success"
},
{
"timestamp": "2026-02-23T05:03:44.138Z",
"message": "Project started on port 9000",
"type": "success"
},
{
"timestamp": "2026-02-23T05:05:11.825Z",
"message": "Project stopped",
"type": "info"
},
{
"timestamp": "2026-02-23T05:09:33.720Z",
"message": "Project started on port 9000",
"type": "success"
},
{
"timestamp": "2026-02-23T05:28:22.481Z",
"message": "Project stopped",
"type": "info"
},
{
"timestamp": "2026-02-23T05:28:23.286Z",
"message": "Project started on port 9000",
"type": "success"
},
{
"timestamp": "2026-02-23T05:28:28.136Z",
"message": "Project stopped",
"type": "info"
},
{
"timestamp": "2026-02-23T05:44:04.885Z",
"message": "Project started on port 9001",
"type": "success"
},
{
"timestamp": "2026-02-23T05:48:30.005Z",
"message": "Project stopped",
"type": "info"
},
{
"timestamp": "2026-02-23T05:48:37.640Z",
"message": "Project started on port 9001",
"type": "success"
},
{
"timestamp": "2026-02-23T05:57:34.667Z",
"message": "Project stopped",
"type": "info"
}
]

View File

@ -1,63 +1 @@
[ []
{
"id": "1771822691510",
"name": "loT-demo",
"description": "智慧楼宇demo演示",
"files": [
{
"name": "assets\\index-csziwVZn.css",
"size": 28375,
"hash": "6e9e99cb0161d7c6ca05aa04d421c424"
},
{
"name": "assets\\index-Ii3hNxW3.js",
"size": 2046014,
"hash": "3cd425f9f0c4e0540ac6c2d63280f821"
},
{
"name": "index.html",
"size": 787,
"hash": "907b8098cc7855605955cddd567f7c74"
},
{
"name": "vite.svg",
"size": 633,
"hash": "3208e1714d3d4aff82769f80952b2960"
}
],
"status": "stopped",
"port": null,
"url": null,
"createdAt": "2026-02-23T04:58:11.965Z",
"updatedAt": "2026-02-23T04:58:11.965Z",
"lastDeployed": "2026-02-23T05:48:33.170Z"
},
{
"id": "1771823016002",
"name": "rushe-demo",
"description": "入舍家装改造demo",
"files": [
{
"name": "assets\\index-DtArR4F6.js",
"size": 416147,
"hash": "9c55c3f179e1bc5bf03baf01ccaa7479"
},
{
"name": "assets\\index-gvDC5mON.css",
"size": 25321,
"hash": "7da89693bde15e541205dbc19aee46a5"
},
{
"name": "index.html",
"size": 572,
"hash": "831e97d4a3f00617843c3c797098842b"
}
],
"status": "stopped",
"port": null,
"url": null,
"createdAt": "2026-02-23T05:03:36.120Z",
"updatedAt": "2026-02-23T05:03:36.120Z",
"lastDeployed": "2026-02-23T05:48:37.640Z"
}
]

View File

@ -120,7 +120,7 @@ curl http://localhost:8888
- **主系统**: http://ashai.com.cn:8888 或 http://49.232.209.156:8888 - **主系统**: http://ashai.com.cn:8888 或 http://49.232.209.156:8888
- **默认账号**: admin - **默认账号**: admin
- **默认密码**: admin123 - **默认密码**: 1221xian
## 常用命令 ## 常用命令

View File

@ -69,6 +69,6 @@ echo 访问地址: http://%DOMAIN%:8888
echo 或者: http://%SERVER_IP%:8888 echo 或者: http://%SERVER_IP%:8888
echo. echo.
echo 默认账号: admin echo 默认账号: admin
echo 默认密码: admin123 echo 默认密码: 1221xian
echo. echo.
pause pause

View File

@ -5,109 +5,88 @@ $DEPLOY_DIR = "/home/ai-auto/auto-deploy-demo"
$DOMAIN = "ashai.com.cn" $DOMAIN = "ashai.com.cn"
Write-Host "==========================================" Write-Host "=========================================="
Write-Host " 自动部署脚本 - 快速Demo演示系统" Write-Host " Auto Deploy Script - Demo System"
Write-Host "==========================================" Write-Host "=========================================="
Write-Host "" Write-Host ""
Write-Host "[提示] 请确保已安装OpenSSH客户端" Write-Host "[Tip] Make sure OpenSSH client is installed"
Write-Host "Windows 10/11 可在 '设置 > 应用 > 可选功能' 中安装" Write-Host "Windows 10/11: Settings > Apps > Optional Features"
Write-Host "" Write-Host ""
Write-Host "即将开始部署,请准备好输入服务器密码: $SERVER_PASS" Write-Host "Server password: $SERVER_PASS"
Write-Host "" Write-Host ""
Read-Host "按Enter键继续..." Read-Host "Press Enter to continue..."
$projectRoot = Split-Path -Parent $PSScriptRoot $projectRoot = Split-Path -Parent $PSScriptRoot
Set-Location $projectRoot Set-Location $projectRoot
Write-Host "" Write-Host ""
Write-Host "[1/8] 测试SSH连接..." Write-Host "[1/8] Testing SSH connection..."
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 "$SERVER_USER@$SERVER_IP" "echo 'SSH连接成功'" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 "$SERVER_USER@$SERVER_IP" "echo 'SSH connected'"
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
Write-Host "[错误] 无法连接到服务器" -ForegroundColor Red Write-Host "[Error] Cannot connect to server" -ForegroundColor Red
Read-Host "按Enter键退出" Read-Host "Press Enter to exit"
exit 1 exit 1
} }
Write-Host "" Write-Host ""
Write-Host "[2/8] 安装Node.js环境..." Write-Host "[2/8] Installing Node.js..."
ssh "$SERVER_USER@$SERVER_IP" @" $installNodeCmd = "if ! command -v node &> /dev/null; then echo 'Installing Node.js...'; curl -fsSL https://rpm.nodesource.com/setup_18.x | sudo bash -; sudo yum install -y nodejs; fi; echo 'Node version:' `$(`node -v); echo 'NPM version:' `$(`npm -v)"
if ! command -v node &> /dev/null; then ssh "$SERVER_USER@$SERVER_IP" $installNodeCmd
echo '正在安装Node.js...'
curl -fsSL https://rpm.nodesource.com/setup_18.x | sudo bash -
sudo yum install -y nodejs
fi
echo 'Node.js版本:' `$(node -v)
echo 'NPM版本:' `$(npm -v)
"@
Write-Host "" Write-Host ""
Write-Host "[3/8] 安装PM2进程管理器..." Write-Host "[3/8] Installing PM2..."
ssh "$SERVER_USER@$SERVER_IP" @" $installPm2Cmd = "if ! command -v pm2 &> /dev/null; then sudo npm install -g pm2; fi; echo 'PM2 version:' `$(`pm2 -v)"
if ! command -v pm2 &> /dev/null; then ssh "$SERVER_USER@$SERVER_IP" $installPm2Cmd
sudo npm install -g pm2
fi
echo 'PM2版本:' `$(pm2 -v)
"@
Write-Host "" Write-Host ""
Write-Host "[4/8] 创建项目目录..." Write-Host "[4/8] Creating project directory..."
ssh "$SERVER_USER@$SERVER_IP" "mkdir -p $DEPLOY_DIR" ssh "$SERVER_USER@$SERVER_IP" "mkdir -p $DEPLOY_DIR"
Write-Host "" Write-Host ""
Write-Host "[5/8] 上传项目文件..." Write-Host "[5/8] Uploading project files..."
Write-Host "上传server目录..." Write-Host "Uploading server directory..."
scp -r "$projectRoot\server" "$SERVER_USER@$SERVER_IP`:$DEPLOY_DIR/" scp -r "$projectRoot\server" "$SERVER_USER@$SERVER_IP`:$DEPLOY_DIR/"
Write-Host "上传projects目录..." Write-Host "Uploading projects directory..."
scp -r "$projectRoot\projects" "$SERVER_USER@$SERVER_IP`:$DEPLOY_DIR/" scp -r "$projectRoot\projects" "$SERVER_USER@$SERVER_IP`:$DEPLOY_DIR/"
Write-Host "上传client/dist目录..." Write-Host "Uploading client/dist directory..."
ssh "$SERVER_USER@$SERVER_IP" "mkdir -p $DEPLOY_DIR/client" ssh "$SERVER_USER@$SERVER_IP" "mkdir -p $DEPLOY_DIR/client/dist"
scp -r "$projectRoot\client\dist\*" "$SERVER_USER@$SERVER_IP`:$DEPLOY_DIR/client/dist/" scp -r "$projectRoot\client\dist\*" "$SERVER_USER@$SERVER_IP`:$DEPLOY_DIR/client/dist/"
Write-Host "上传配置文件..." Write-Host "Uploading config files..."
scp "$projectRoot\package.json" "$SERVER_USER@$SERVER_IP`:$DEPLOY_DIR/" scp "$projectRoot\package.json" "$SERVER_USER@$SERVER_IP`:$DEPLOY_DIR/"
scp "$projectRoot\package-lock.json" "$SERVER_USER@$SERVER_IP`:$DEPLOY_DIR/" scp "$projectRoot\package-lock.json" "$SERVER_USER@$SERVER_IP`:$DEPLOY_DIR/"
scp "$projectRoot\.env" "$SERVER_USER@$SERVER_IP`:$DEPLOY_DIR/" scp "$projectRoot\.env" "$SERVER_USER@$SERVER_IP`:$DEPLOY_DIR/"
Write-Host "" Write-Host ""
Write-Host "[6/8] 安装项目依赖..." Write-Host "[6/8] Installing dependencies..."
ssh "$SERVER_USER@$SERVER_IP" "cd $DEPLOY_DIR && npm install --production" ssh "$SERVER_USER@$SERVER_IP" "cd $DEPLOY_DIR && npm install --production"
Write-Host "" Write-Host ""
Write-Host "[7/8] 配置防火墙..." Write-Host "[7/8] Configuring firewall..."
ssh "$SERVER_USER@$SERVER_IP" @" $firewallCmd = "sudo firewall-cmd --permanent --add-port=8888/tcp; sudo firewall-cmd --permanent --add-port=9000-9100/tcp; sudo firewall-cmd --reload; echo 'Firewall configured'"
sudo firewall-cmd --permanent --add-port=8888/tcp ssh "$SERVER_USER@$SERVER_IP" $firewallCmd
sudo firewall-cmd --permanent --add-port=9000-9100/tcp
sudo firewall-cmd --reload
echo '防火墙配置完成'
"@
Write-Host "" Write-Host ""
Write-Host "[8/8] 启动服务..." Write-Host "[8/8] Starting service..."
ssh "$SERVER_USER@$SERVER_IP" @" $startCmd = "cd $DEPLOY_DIR; pm2 delete all 2>/dev/null; pm2 start server/index.js --name 'auto-deploy-demo'; pm2 save; pm2 startup | tail -n 1 | sudo bash 2>/dev/null; echo 'Service started'"
cd $DEPLOY_DIR ssh "$SERVER_USER@$SERVER_IP" $startCmd
pm2 delete all 2>/dev/null || true
pm2 start server/index.js --name 'auto-deploy-demo'
pm2 save
pm2 startup | tail -n 1 | sudo bash 2>/dev/null || true
echo '服务启动完成'
"@
Write-Host "" Write-Host ""
Write-Host "==========================================" Write-Host "=========================================="
Write-Host " 部署完成!" Write-Host " Deployment Complete!"
Write-Host "==========================================" Write-Host "=========================================="
Write-Host "" Write-Host ""
Write-Host "访问地址: http://$DOMAIN`:8888" -ForegroundColor Green Write-Host "URL: http://$DOMAIN`:8888" -ForegroundColor Green
Write-Host "或者: http://$SERVER_IP`:8888" -ForegroundColor Green Write-Host "Or: http://$SERVER_IP`:8888" -ForegroundColor Green
Write-Host "" Write-Host ""
Write-Host "默认账号: admin" Write-Host "Username: admin"
Write-Host "默认密码: admin123" Write-Host "Password: 1221xian"
Write-Host "" Write-Host ""
Write-Host "常用命令:" Write-Host "Useful commands:"
Write-Host " 查看日志: ssh $SERVER_USER@$SERVER_IP 'pm2 logs auto-deploy-demo'" Write-Host " View logs: ssh $SERVER_USER@$SERVER_IP 'pm2 logs auto-deploy-demo'"
Write-Host " 重启服务: ssh $SERVER_USER@$SERVER_IP 'pm2 restart auto-deploy-demo'" Write-Host " Restart: ssh $SERVER_USER@$SERVER_IP 'pm2 restart auto-deploy-demo'"
Write-Host " 停止服务: ssh $SERVER_USER@$SERVER_IP 'pm2 stop auto-deploy-demo'" Write-Host " Stop: ssh $SERVER_USER@$SERVER_IP 'pm2 stop auto-deploy-demo'"
Write-Host "" Write-Host ""
Read-Host "按Enter键退出" Read-Host "Press Enter to exit"

View File

@ -83,7 +83,7 @@ echo "访问地址: http://$DOMAIN:8888"
echo "或者: http://$SERVER_IP:8888" echo "或者: http://$SERVER_IP:8888"
echo "" echo ""
echo "默认账号: admin" echo "默认账号: admin"
echo "默认密码: admin123" echo "默认密码: 1221xian"
echo "" echo ""
echo "常用命令:" echo "常用命令:"
echo " 查看日志: ssh $SERVER_USER@$SERVER_IP 'pm2 logs auto-deploy-demo'" echo " 查看日志: ssh $SERVER_USER@$SERVER_IP 'pm2 logs auto-deploy-demo'"

211
package-lock.json generated
View File

@ -16,6 +16,7 @@
"crypto": "^1.0.1", "crypto": "^1.0.1",
"dotenv": "^17.3.1", "dotenv": "^17.3.1",
"express": "^4.18.2", "express": "^4.18.2",
"http-proxy-middleware": "^3.0.5",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1" "multer": "^1.4.5-lts.1"
}, },
@ -32,6 +33,24 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@types/http-proxy": {
"version": "1.17.17",
"resolved": "https://registry.npmmirror.com/@types/http-proxy/-/http-proxy-1.17.17.tgz",
"integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "25.3.2",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-25.3.2.tgz",
"integrity": "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.18.0"
}
},
"node_modules/accepts": { "node_modules/accepts": {
"version": "1.3.8", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@ -191,6 +210,18 @@
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
}, },
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/buffer-crc32": { "node_modules/buffer-crc32": {
"version": "0.2.13", "version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
@ -620,6 +651,12 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"license": "MIT"
},
"node_modules/events-universal": { "node_modules/events-universal": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
@ -678,6 +715,18 @@
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="
}, },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/finalhandler": { "node_modules/finalhandler": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
@ -695,6 +744,26 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/forwarded": { "node_modules/forwarded": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@ -853,6 +922,60 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/http-proxy": {
"version": "1.18.1",
"resolved": "https://registry.npmmirror.com/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"license": "MIT",
"dependencies": {
"eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/http-proxy-middleware": {
"version": "3.0.5",
"resolved": "https://registry.npmmirror.com/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz",
"integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==",
"license": "MIT",
"dependencies": {
"@types/http-proxy": "^1.17.15",
"debug": "^4.3.6",
"http-proxy": "^1.18.1",
"is-glob": "^4.0.3",
"is-plain-object": "^5.0.0",
"micromatch": "^4.0.8"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/http-proxy-middleware/node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/http-proxy-middleware/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -887,6 +1010,15 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-fullwidth-code-point": { "node_modules/is-fullwidth-code-point": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@ -896,6 +1028,36 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/isarray": { "node_modules/isarray": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@ -1056,6 +1218,19 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/mime": { "node_modules/mime": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@ -1206,6 +1381,18 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
}, },
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/process-nextick-args": { "node_modules/process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@ -1289,6 +1476,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"license": "MIT"
},
"node_modules/rxjs": { "node_modules/rxjs": {
"version": "7.8.2", "version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
@ -1559,6 +1752,18 @@
"b4a": "^1.6.4" "b4a": "^1.6.4"
} }
}, },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/toidentifier": { "node_modules/toidentifier": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@ -1599,6 +1804,12 @@
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
}, },
"node_modules/undici-types": {
"version": "7.18.2",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"license": "MIT"
},
"node_modules/unpipe": { "node_modules/unpipe": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

View File

@ -25,6 +25,7 @@
"crypto": "^1.0.1", "crypto": "^1.0.1",
"dotenv": "^17.3.1", "dotenv": "^17.3.1",
"express": "^4.18.2", "express": "^4.18.2",
"http-proxy-middleware": "^3.0.5",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1" "multer": "^1.4.5-lts.1"
}, },

View File

@ -6,11 +6,18 @@ const config = {
projectPortStart: parseInt(process.env.PROJECT_PORT_START, 10) || 9000, projectPortStart: parseInt(process.env.PROJECT_PORT_START, 10) || 9000,
projectPortEnd: parseInt(process.env.PROJECT_PORT_END, 10) || 9100, projectPortEnd: parseInt(process.env.PROJECT_PORT_END, 10) || 9100,
getProjectUrl(port) { getProjectUrl(port, projectPath = '') {
let baseUrl;
if (this.baseDomain) { if (this.baseDomain) {
return `http://${this.baseDomain}:${port}`; baseUrl = `http://${this.baseDomain}:${port}`;
} else {
baseUrl = `http://localhost:${port}`;
} }
return `http://localhost:${port}`;
if (projectPath) {
return `${baseUrl}${projectPath}`;
}
return baseUrl;
}, },
getBaseUrl() { getBaseUrl() {

View File

@ -2,6 +2,7 @@ const express = require('express');
const { authMiddleware } = require('../middleware/auth'); const { authMiddleware } = require('../middleware/auth');
const projectService = require('../services/projectService'); const projectService = require('../services/projectService');
const processManager = require('../services/processManager'); const processManager = require('../services/processManager');
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -18,11 +19,13 @@ router.post('/:id/start', authMiddleware, async (req, res) => {
try { try {
const result = await processManager.startProject(project); const result = await processManager.startProject(project);
projectService.updateProjectStatus(project.id, 'running', result.port, result.url); const url = config.getProjectUrl(result.port, project.path);
projectService.updateProjectStatus(project.id, 'running', result.port, url);
res.json({ res.json({
message: 'Project started successfully', message: 'Project started successfully',
url: result.url, url: url,
port: result.port port: result.port
}); });
} catch (error) { } catch (error) {

View File

@ -40,7 +40,7 @@ router.get('/:id', authMiddleware, (req, res) => {
}); });
router.post('/', authMiddleware, upload.array('files'), (req, res) => { router.post('/', authMiddleware, upload.array('files'), (req, res) => {
const { name, description } = req.body; const { name, description, path: projectPath } = req.body;
const files = req.files; const files = req.files;
if (!name) { if (!name) {
@ -51,9 +51,15 @@ router.post('/', authMiddleware, upload.array('files'), (req, res) => {
return res.status(400).json({ error: 'At least one file is required' }); return res.status(400).json({ error: 'At least one file is required' });
} }
let normalizedPath = projectPath ? projectPath.trim() : '';
if (normalizedPath && !normalizedPath.startsWith('/')) {
normalizedPath = '/' + normalizedPath;
}
const project = projectService.createProject({ const project = projectService.createProject({
name, name,
description: description || '', description: description || '',
path: normalizedPath,
files files
}); });

View File

@ -73,19 +73,33 @@ const detectProjectType = (projectDir) => {
return 'static'; return 'static';
}; };
const startStaticServer = (projectDir, port) => { const startStaticServer = (projectDir, port, projectPath = '') => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const app = express(); const app = express();
app.use(express.static(projectDir));
app.get('*', (req, res) => { if (projectPath) {
const indexPath = path.join(projectDir, 'index.html'); app.use(projectPath, express.static(projectDir));
if (fs.existsSync(indexPath)) {
res.sendFile(indexPath); app.get(`${projectPath}/*`, (req, res) => {
} else { const indexPath = path.join(projectDir, 'index.html');
res.status(404).send('index.html not found'); if (fs.existsSync(indexPath)) {
} res.sendFile(indexPath);
}); } else {
res.status(404).send('index.html not found');
}
});
} else {
app.use(express.static(projectDir));
app.get('*', (req, res) => {
const indexPath = path.join(projectDir, 'index.html');
if (fs.existsSync(indexPath)) {
res.sendFile(indexPath);
} else {
res.status(404).send('index.html not found');
}
});
}
const server = app.listen(port, () => { const server = app.listen(port, () => {
resolve({ server, port }); resolve({ server, port });
@ -110,12 +124,13 @@ const startProject = async (project) => {
const port = await findAvailablePort(); const port = await findAvailablePort();
const projectType = detectProjectType(projectDir); const projectType = detectProjectType(projectDir);
const projectPath = project.path || '';
let processInfo = null; let processInfo = null;
switch (projectType) { switch (projectType) {
case 'static': case 'static':
const { server } = await startStaticServer(projectDir, port); const { server } = await startStaticServer(projectDir, port, projectPath);
processInfo = { processInfo = {
type: 'static', type: 'static',
server, server,
@ -162,7 +177,7 @@ const startProject = async (project) => {
runningProcesses.set(project.id, processInfo); runningProcesses.set(project.id, processInfo);
const url = config.getProjectUrl(port); const url = config.getProjectUrl(port, projectPath);
return { port, url }; return { port, url };
}; };

View File

@ -62,7 +62,7 @@ const getProjectById = (id) => {
return projects.find(p => p.id === id); return projects.find(p => p.id === id);
}; };
const createProject = ({ name, description, files }) => { const createProject = ({ name, description, path: projectPath, files }) => {
const projects = getProjects(); const projects = getProjects();
const id = Date.now().toString(); const id = Date.now().toString();
@ -100,6 +100,7 @@ const createProject = ({ name, description, files }) => {
id, id,
name, name,
description, description,
path: projectPath || '',
files: allFiles.map(f => ({ files: allFiles.map(f => ({
name: path.relative(projectDir, f), name: path.relative(projectDir, f),
size: fs.statSync(f).size, size: fs.statSync(f).size,