2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 03:07:20 +08:00

feat: 支持更多项目类型

This commit is contained in:
2026-01-16 02:17:18 +08:00
parent 0cdf62af31
commit d2c4ab3bb4
4 changed files with 354 additions and 21 deletions

View File

@@ -122,8 +122,8 @@ func (r *environmentRepo) InstalledVersion(typ, slug string) string {
// go version go1.21.0 linux/amd64 -> 1.21.0
version, err = shell.Exec(filepath.Join(basePath, "bin", "go") + " version | awk '{print $3}' | sed 's/go//'")
case "java":
// openjdk version "17.0.8" 2023-07-18 LTS -> 17.0.8
version, err = shell.Exec(filepath.Join(basePath, "bin", "java") + " -version 2>&1 | head -n 1 | awk -F'\"' '{print $2}'")
// OpenJDK Runtime Environment Corretto-21.0.9.11.1 (build 21.0.9+11-LTS) -> 21.0.9.11.1
version, err = shell.Exec(filepath.Join(basePath, "bin", "java") + ` -version 2>&1 | sed -n 's/.*Corretto-\([0-9.]*\).*/\1/p' | head -n 1`)
case "nodejs":
// v20.10.0 -> 20.10.0
version, err = shell.Exec(filepath.Join(basePath, "bin", "node") + " -v | sed 's/v//'")

View File

@@ -225,14 +225,44 @@ func (s *HomeService) InstalledEnvironment(w http.ResponseWriter, r *http.Reques
mysqlInstalled, _ := s.appRepo.IsInstalled("slug IN ?", []string{"mysql", "mariadb", "percona"})
postgresqlInstalled, _ := s.appRepo.IsInstalled("slug = ?", "postgresql")
// Go 版本
var goData []types.LV
for _, slug := range s.environmentRepo.InstalledSlugs("go") {
ver := s.environmentRepo.InstalledVersion("go", slug)
goData = append(goData, types.LV{Value: slug, Label: fmt.Sprintf("Go %s", ver)})
}
// Java 版本
var javaData []types.LV
for _, slug := range s.environmentRepo.InstalledSlugs("java") {
ver := s.environmentRepo.InstalledVersion("java", slug)
javaData = append(javaData, types.LV{Value: slug, Label: fmt.Sprintf("Java %s", ver)})
}
// Node.js 版本
var nodejsData []types.LV
for _, slug := range s.environmentRepo.InstalledSlugs("nodejs") {
ver := s.environmentRepo.InstalledVersion("nodejs", slug)
nodejsData = append(nodejsData, types.LV{Value: slug, Label: fmt.Sprintf("Node.js %s", ver)})
}
// PHP 版本
var phpData []types.LVInt
var dbData []types.LV
dbData = append(dbData, types.LV{Value: "0", Label: s.t.Get("Not used")})
for _, slug := range s.environmentRepo.InstalledSlugs("php") {
ver := s.environmentRepo.InstalledVersion("php", slug)
phpData = append(phpData, types.LVInt{Value: cast.ToInt(slug), Label: fmt.Sprintf("PHP %s", ver)})
}
// Python 版本
var pythonData []types.LV
for _, slug := range s.environmentRepo.InstalledSlugs("python") {
ver := s.environmentRepo.InstalledVersion("python", slug)
pythonData = append(pythonData, types.LV{Value: slug, Label: fmt.Sprintf("Python %s", ver)})
}
// 数据库
var dbData []types.LV
dbData = append(dbData, types.LV{Value: "0", Label: s.t.Get("Not used")})
if mysqlInstalled {
dbData = append(dbData, types.LV{Value: "mysql", Label: "MySQL"})
}
@@ -243,7 +273,11 @@ func (s *HomeService) InstalledEnvironment(w http.ResponseWriter, r *http.Reques
webserver, _ := s.settingRepo.Get(biz.SettingKeyWebserver)
Success(w, chix.M{
"webserver": webserver,
"go": goData,
"java": javaData,
"nodejs": nodejsData,
"php": phpData,
"python": pythonData,
"db": dbData,
})
}

View File

@@ -10,6 +10,36 @@ const type = defineModel<string>('type', { type: String, required: true })
const { $gettext } = useGettext()
// Go 运行模式
const goModes = [
{ label: $gettext('Source Code'), value: 'source' },
{ label: $gettext('Binary'), value: 'binary' }
]
// Java 框架预设
const javaFrameworks = [
{ label: $gettext('Custom'), value: 'custom', command: '' },
{ label: 'Spring Boot (JAR)', value: 'spring-boot-jar', command: '-jar app.jar' },
{ label: 'Spring Boot (WAR)', value: 'spring-boot-war', command: '-jar app.war' },
{ label: 'Quarkus', value: 'quarkus', command: '-jar quarkus-run.jar' },
{ label: 'Micronaut', value: 'micronaut', command: '-jar app.jar' },
{ label: 'Vert.x', value: 'vertx', command: '-jar app.jar' },
{ label: 'Dropwizard', value: 'dropwizard', command: 'server config.yml' }
]
// Node.js 框架预设
const nodejsFrameworks = [
{ label: $gettext('Custom'), value: 'custom', command: '' },
{ label: 'Express', value: 'express', command: 'app.js' },
{ label: 'Koa', value: 'koa', command: 'app.js' },
{ label: 'Fastify', value: 'fastify', command: 'app.js' },
{ label: 'NestJS', value: 'nestjs', command: 'dist/main.js' },
{ label: 'Next.js', value: 'nextjs', command: 'node_modules/.bin/next start' },
{ label: 'Nuxt.js', value: 'nuxtjs', command: 'node_modules/.bin/nuxt start' },
{ label: 'Hapi', value: 'hapi', command: 'server.js' },
{ label: 'AdonisJS', value: 'adonisjs', command: 'server.js' }
]
// PHP 框架预设
const phpFrameworks = [
{ label: $gettext('Custom'), value: 'custom', command: '' },
@@ -22,6 +52,18 @@ const phpFrameworks = [
{ label: 'RoadRunner', value: 'roadrunner', command: 'vendor/bin/rr serve' }
]
// Python 框架预设
const pythonFrameworks = [
{ label: $gettext('Custom'), value: 'custom', command: '' },
{ label: 'Django', value: 'django', command: 'manage.py runserver 0.0.0.0:8000' },
{ label: 'Flask', value: 'flask', command: '-m flask run --host=0.0.0.0' },
{ label: 'FastAPI (Uvicorn)', value: 'fastapi', command: '-m uvicorn main:app --host 0.0.0.0' },
{ label: 'Tornado', value: 'tornado', command: 'app.py' },
{ label: 'Sanic', value: 'sanic', command: '-m sanic server.app --host=0.0.0.0' },
{ label: 'aiohttp', value: 'aiohttp', command: 'app.py' },
{ label: 'Gunicorn', value: 'gunicorn', command: '-m gunicorn -w 4 app:app' }
]
const createModel = ref({
name: '',
type: '',
@@ -31,46 +73,163 @@ const createModel = ref({
user: 'www'
})
// Go 特有字段
const goOptions = ref({
mode: 'source' as string,
version: '' as string,
entryFile: 'main.go' as string
})
// Java 特有字段
const javaOptions = ref({
version: '' as string,
framework: 'custom'
})
// Node.js 特有字段
const nodejsOptions = ref({
version: '' as string,
framework: 'custom'
})
// PHP 特有字段
const phpOptions = ref({
version: null as number | null,
framework: 'custom'
})
// Python 特有字段
const pythonOptions = ref({
version: '' as string,
framework: 'custom'
})
const showPathSelector = ref(false)
const pathSelectorPath = ref('/opt/ace/projects')
const { data: installedEnvironment } = useRequest(home.installedEnvironment, {
initialData: {
php: []
go: [],
java: [],
nodejs: [],
php: [],
python: []
}
})
// Go 版本选项
const goVersionOptions = computed(() => {
return installedEnvironment.value?.go || []
})
// Java 版本选项
const javaVersionOptions = computed(() => {
return installedEnvironment.value?.java || []
})
// Node.js 版本选项
const nodejsVersionOptions = computed(() => {
return installedEnvironment.value?.nodejs || []
})
// PHP 版本选项
const phpVersionOptions = computed(() => {
return installedEnvironment.value?.php || []
})
// 根据 PHP 版本和框架生成启动命令
// Python 版本选项
const pythonVersionOptions = computed(() => {
return installedEnvironment.value?.python || []
})
// 根据语言版本和框架生成启动命令
const generateCommand = () => {
if (type.value !== 'php' || !phpOptions.value.version) {
return
switch (type.value) {
case 'go': {
if (goOptions.value.mode === 'source') {
// 源码模式
if (!goOptions.value.version || !goOptions.value.entryFile) return
const goBin = `go${goOptions.value.version}`
createModel.value.exec_start = `${goBin} run ${goOptions.value.entryFile}`
} else {
// 二进制模式
const rootDir = createModel.value.root_dir || `/opt/ace/projects/${createModel.value.name || 'project'}`
createModel.value.exec_start = `${rootDir}/main`
}
break
}
case 'java': {
if (!javaOptions.value.version) return
const framework = javaFrameworks.find((f) => f.value === javaOptions.value.framework)
if (!framework || framework.value === 'custom') return
const javaBin = `java${javaOptions.value.version}`
createModel.value.exec_start = `${javaBin} ${framework.command}`
break
}
case 'nodejs': {
if (!nodejsOptions.value.version) return
const framework = nodejsFrameworks.find((f) => f.value === nodejsOptions.value.framework)
if (!framework || framework.value === 'custom') return
const nodeBin = `node${nodejsOptions.value.version}`
createModel.value.exec_start = `${nodeBin} ${framework.command}`
break
}
case 'php': {
if (!phpOptions.value.version) return
const framework = phpFrameworks.find((f) => f.value === phpOptions.value.framework)
if (!framework || framework.value === 'custom') return
const phpBin = `php${phpOptions.value.version}`
createModel.value.exec_start = `${phpBin} ${framework.command}`
break
}
case 'python': {
if (!pythonOptions.value.version) return
const framework = pythonFrameworks.find((f) => f.value === pythonOptions.value.framework)
if (!framework || framework.value === 'custom') return
const pythonBin = `python${pythonOptions.value.version}`
createModel.value.exec_start = `${pythonBin} ${framework.command}`
break
}
}
const framework = phpFrameworks.find((f) => f.value === phpOptions.value.framework)
if (!framework || framework.value === 'custom') {
return
}
const phpBin = `php${phpOptions.value.version}`
createModel.value.exec_start = `${phpBin} ${framework.command}`
}
// 监听 Go 选项变化
watch(
() => [goOptions.value.mode, goOptions.value.version, goOptions.value.entryFile, createModel.value.root_dir, createModel.value.name],
() => {
if (type.value === 'go') generateCommand()
}
)
// 监听 Java 版本和框架变化
watch(
() => [javaOptions.value.version, javaOptions.value.framework],
() => {
if (type.value === 'java') generateCommand()
}
)
// 监听 Node.js 版本和框架变化
watch(
() => [nodejsOptions.value.version, nodejsOptions.value.framework],
() => {
if (type.value === 'nodejs') generateCommand()
}
)
// 监听 PHP 版本和框架变化
watch(
() => [phpOptions.value.version, phpOptions.value.framework],
() => {
generateCommand()
if (type.value === 'php') generateCommand()
}
)
// 监听 Python 版本和框架变化
watch(
() => [pythonOptions.value.version, pythonOptions.value.framework],
() => {
if (type.value === 'python') generateCommand()
}
)
@@ -103,10 +262,27 @@ const handleCreate = async () => {
exec_start: '',
user: 'www'
}
goOptions.value = {
mode: 'source',
version: '',
entryFile: 'main.go'
}
javaOptions.value = {
version: '',
framework: 'custom'
}
nodejsOptions.value = {
version: '',
framework: 'custom'
}
phpOptions.value = {
version: null,
framework: 'custom'
}
pythonOptions.value = {
version: '',
framework: 'custom'
}
})
}
@@ -114,7 +290,11 @@ const handleCreate = async () => {
const modalTitle = computed(() => {
const titles: Record<string, string> = {
general: $gettext('Create General Project'),
php: $gettext('Create PHP Project')
go: $gettext('Create Go Project'),
java: $gettext('Create Java Project'),
nodejs: $gettext('Create Node.js Project'),
php: $gettext('Create PHP Project'),
python: $gettext('Create Python Project')
}
return titles[type.value] || $gettext('Create Project')
})
@@ -161,6 +341,99 @@ const modalTitle = computed(() => {
</n-input-group>
</n-form-item>
<!-- Go 类型特有字段 -->
<template v-if="type === 'go'">
<n-form-item :label="$gettext('Run Mode')">
<n-radio-group v-model:value="goOptions.mode">
<n-radio-button
v-for="mode in goModes"
:key="mode.value"
:value="mode.value"
:label="mode.label"
/>
</n-radio-group>
</n-form-item>
<!-- 源码模式 -->
<template v-if="goOptions.mode === 'source'">
<n-row :gutter="[24, 0]">
<n-col :span="12">
<n-form-item :label="$gettext('Go Version')">
<n-select
v-model:value="goOptions.version"
:options="goVersionOptions"
:placeholder="$gettext('Select Go Version')"
@keydown.enter.prevent
/>
</n-form-item>
</n-col>
<n-col :span="12">
<n-form-item :label="$gettext('Entry File')">
<n-input
v-model:value="goOptions.entryFile"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('e.g., main.go, cmd/server/main.go')"
/>
</n-form-item>
</n-col>
</n-row>
</template>
</template>
<!-- Java 类型特有字段 -->
<template v-if="type === 'java'">
<n-row :gutter="[24, 0]">
<n-col :span="12">
<n-form-item :label="$gettext('Java Version')">
<n-select
v-model:value="javaOptions.version"
:options="javaVersionOptions"
:placeholder="$gettext('Select Java Version')"
@keydown.enter.prevent
/>
</n-form-item>
</n-col>
<n-col :span="12">
<n-form-item :label="$gettext('Framework')">
<n-select
v-model:value="javaOptions.framework"
:options="javaFrameworks"
:placeholder="$gettext('Select Framework')"
@keydown.enter.prevent
/>
</n-form-item>
</n-col>
</n-row>
</template>
<!-- Node.js 类型特有字段 -->
<template v-if="type === 'nodejs'">
<n-row :gutter="[24, 0]">
<n-col :span="12">
<n-form-item :label="$gettext('Node.js Version')">
<n-select
v-model:value="nodejsOptions.version"
:options="nodejsVersionOptions"
:placeholder="$gettext('Select Node.js Version')"
@keydown.enter.prevent
/>
</n-form-item>
</n-col>
<n-col :span="12">
<n-form-item :label="$gettext('Framework')">
<n-select
v-model:value="nodejsOptions.framework"
:options="nodejsFrameworks"
:placeholder="$gettext('Select Framework')"
@keydown.enter.prevent
/>
</n-form-item>
</n-col>
</n-row>
</template>
<!-- PHP 类型特有字段 -->
<template v-if="type === 'php'">
<n-row :gutter="[24, 0]">
@@ -187,6 +460,32 @@ const modalTitle = computed(() => {
</n-row>
</template>
<!-- Python 类型特有字段 -->
<template v-if="type === 'python'">
<n-row :gutter="[24, 0]">
<n-col :span="12">
<n-form-item :label="$gettext('Python Version')">
<n-select
v-model:value="pythonOptions.version"
:options="pythonVersionOptions"
:placeholder="$gettext('Select Python Version')"
@keydown.enter.prevent
/>
</n-form-item>
</n-col>
<n-col :span="12">
<n-form-item :label="$gettext('Framework')">
<n-select
v-model:value="pythonOptions.framework"
:options="pythonFrameworks"
:placeholder="$gettext('Select Framework')"
@keydown.enter.prevent
/>
</n-form-item>
</n-col>
</n-row>
</template>
<n-form-item path="user" :label="$gettext('Run User')">
<n-select
v-model:value="createModel.user"

View File

@@ -20,11 +20,11 @@ const editId = ref(0)
<n-tabs v-model:value="currentTab" animated>
<n-tab name="all" :tab="$gettext('All')" />
<n-tab name="general" :tab="$gettext('General')" />
<n-tab name="php" :tab="$gettext('PHP')" />
<n-tab name="java" :tab="$gettext('Java')" />
<n-tab name="go" :tab="$gettext('Go')" />
<n-tab name="python" :tab="$gettext('Python')" />
<n-tab name="java" :tab="$gettext('Java')" />
<n-tab name="nodejs" :tab="$gettext('Node.js')" />
<n-tab name="php" :tab="$gettext('PHP')" />
<n-tab name="python" :tab="$gettext('Python')" />
</n-tabs>
</template>
<list-view