feat: Website Add Composer support (#9997)

Refs https://github.com/1Panel-dev/1Panel/issues/6030
This commit is contained in:
CityFun 2025-08-14 17:46:54 +08:00 committed by GitHub
parent 5b62a0f2b3
commit 6b81ae1b7b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 351 additions and 33 deletions

View file

@ -1121,3 +1121,23 @@ func (b *BaseApi) OperateCrossSiteAccess(c *gin.Context) {
}
helper.Success(c)
}
// @Tags Website
// @Summary Exec Composer
// @Accept json
// @Param request body request.ExecComposerReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /websites/exec/composer [post]
func (b *BaseApi) ExecComposer(c *gin.Context) {
var req request.ExecComposerReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := websiteService.ExecComposer(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}

View file

@ -152,22 +152,22 @@ type WebsiteDomainDelete struct {
}
type WebsiteHTTPSOp struct {
WebsiteID uint `json:"websiteId" validate:"required"`
Enable bool `json:"enable"`
WebsiteSSLID uint `json:"websiteSSLId"`
Type string `json:"type" validate:"oneof=existed auto manual"`
PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"`
PrivateKeyPath string `json:"privateKeyPath"`
CertificatePath string `json:"certificatePath"`
ImportType string `json:"importType"`
HttpConfig string `json:"httpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
Hsts bool `json:"hsts"`
HstsIncludeSubDomains bool `json:"hstsIncludeSubDomains"`
HttpsPorts []int `json:"httpsPorts"`
Http3 bool `json:"http3"`
WebsiteID uint `json:"websiteId" validate:"required"`
Enable bool `json:"enable"`
WebsiteSSLID uint `json:"websiteSSLId"`
Type string `json:"type" validate:"oneof=existed auto manual"`
PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"`
PrivateKeyPath string `json:"privateKeyPath"`
CertificatePath string `json:"certificatePath"`
ImportType string `json:"importType"`
HttpConfig string `json:"httpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
Hsts bool `json:"hsts"`
HstsIncludeSubDomains bool `json:"hstsIncludeSubDomains"`
HttpsPorts []int `json:"httpsPorts"`
Http3 bool `json:"http3"`
}
type WebsiteNginxUpdate struct {
@ -299,3 +299,13 @@ type CrossSiteAccessOp struct {
WebsiteID uint `json:"websiteID" validate:"required"`
Operation string `json:"operation" validate:"required,oneof=Enable Disable"`
}
type ExecComposerReq struct {
Command string `json:"command" validate:"required"`
ExtCommand string `json:"extCommand"`
Mirror string `json:"mirror" validate:"required"`
Dir string `json:"dir" validate:"required"`
User string `json:"user" validate:"required"`
WebsiteID uint `json:"websiteID" validate:"required"`
TaskID string `json:"taskID" validate:"required"`
}

View file

@ -126,6 +126,8 @@ type IWebsiteService interface {
ChangeDatabase(req request.ChangeDatabase) error
OperateCrossSiteAccess(req request.CrossSiteAccessOp) error
ExecComposer(req request.ExecComposerReq) error
}
func NewIWebsiteService() IWebsiteService {
@ -466,15 +468,15 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
website.Protocol = constant.ProtocolHTTPS
website.WebsiteSSLID = create.WebsiteSSLID
appSSLReq := request.WebsiteHTTPSOp{
WebsiteID: website.ID,
Enable: true,
WebsiteSSLID: websiteModel.ID,
Type: "existed",
HttpConfig: "HTTPToHTTPS",
SSLProtocol: []string{"TLSv1.3", "TLSv1.2"},
Algorithm: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED",
Hsts: true,
HstsIncludeSubDomains: true,
WebsiteID: website.ID,
Enable: true,
WebsiteSSLID: websiteModel.ID,
Type: "existed",
HttpConfig: "HTTPToHTTPS",
SSLProtocol: []string{"TLSv1.3", "TLSv1.2"},
Algorithm: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED",
Hsts: true,
HstsIncludeSubDomains: true,
}
if err = applySSL(website, *websiteModel, appSSLReq); err != nil {
return err
@ -960,7 +962,6 @@ func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS,
if p.Name == "add_header" && len(p.Params) > 0 {
if p.Params[0] == "Strict-Transport-Security" {
res.Hsts = true
//增加HSTS下子域名检查逻辑
if len(p.Params) > 1 {
hstsValue := p.Params[1]
if strings.Contains(hstsValue, "includeSubDomains") {
@ -3319,3 +3320,50 @@ func (w WebsiteService) OperateCrossSiteAccess(req request.CrossSiteAccessOp) er
}
return nil
}
func (w WebsiteService) ExecComposer(req request.ExecComposerReq) error {
website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
if err != nil {
return err
}
sitePath := GetSitePath(website, SiteDir)
if !strings.Contains(req.Dir, sitePath) {
return buserr.New("ErrWebsiteDir")
}
if !files.NewFileOp().Stat(path.Join(req.Dir, "composer.json")) {
return buserr.New("ErrComposerFileNotFound")
}
if task.CheckResourceTaskIsExecuting(task.TaskExec, req.Command, website.ID) {
return buserr.New("ErrInstallExtension")
}
runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(website.RuntimeID))
if err != nil {
return err
}
var command string
if req.Command != "custom" {
command = fmt.Sprintf("%s %s", req.Command, req.ExtCommand)
} else {
command = req.ExtCommand
}
resourceName := fmt.Sprintf("composer %s", command)
composerTask, err := task.NewTaskWithOps(resourceName, task.TaskExec, req.Command, req.TaskID, website.ID)
if err != nil {
return err
}
cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*composerTask), cmd.WithTimeout(20*time.Minute))
siteDir, _ := settingRepo.Get(settingRepo.WithByKey("WEBSITE_DIR"))
execDir := strings.ReplaceAll(req.Dir, siteDir.Value, "/www")
composerTask.AddSubTask("", func(t *task.Task) error {
cmdStr := fmt.Sprintf("docker exec -u %s %s sh -c 'composer config -g repo.packagist composer %s && composer %s --working-dir=%s'", req.User, runtime.ContainerName, req.Mirror, command, execDir)
err = cmdMgr.RunBashCf(cmdStr)
if err != nil {
return err
}
return nil
}, nil)
go func() {
_ = composerTask.Execute()
}()
return nil
}

View file

@ -67,6 +67,7 @@ const (
TaskPush = "TaskPush"
TaskClean = "TaskClean"
TaskHandle = "TaskHandle"
TaskExec = "TaskExec"
)
const (

View file

@ -123,6 +123,8 @@ ErrBuildDirNotFound: 'The build directory does not exist'
ErrImageNotExist: 'The operating environment {{ .name }} image does not exist, please re-edit the operating environment'
ErrProxyIsUsed: "Load balancing has been used by reverse proxy, cannot be deleted"
ErrSSLValid: 'Certificate file is abnormal, please check the certificate status!'
ErrWebsiteDir: "Please select a directory within the website directory."
ErrComposerFileNotFound: "composer.json file does not exist"
#ssl
ErrSSLCannotDelete: 'The {{ .name }} certificate is being used by a website and cannot be deleted'
@ -328,6 +330,7 @@ SubTask: 'Subtask'
RuntimeExtension: 'Runtime Environment Extension'
TaskIsExecuting: 'Task is running'
CustomAppstore: 'Custom application warehouse'
TaskExec: 'Execute'
# task - ai
OllamaModelPull: 'Pull Ollama model {{ .name }}'

View file

@ -123,6 +123,8 @@ ErrBuildDirNotFound: 'ビルド ディレクトリが存在しません'
ErrImageNotExist: 'オペレーティング環境 {{ .name }} イメージが存在しません。オペレーティング環境を再編集してください'
ErrProxyIsUsed: "ロードバランシングはリバースプロキシによって使用されているため、削除できません"
ErrSSLValid: '証明書ファイルが異常です、証明書の状態を確認してください!'
ErrWebsiteDir: "ウェブサイトディレクトリ内のディレクトリを選択してください。"
ErrComposerFileNotFound: "composer.json ファイルが存在しません"
#ssl
ErrSSLCannotDelete: '{{ .name }} 証明書は Web サイトで使用されているため、削除できません'
@ -328,6 +330,7 @@ SubTask: 'サブタスク'
RuntimeExtension: 'ランタイム環境拡張'
TaskIsExecuting: 'タスクは実行中です'
CustomAppstore: 'カスタム アプリケーション ウェアハウス'
TaskExec: '実行'
#task - ai
OllamaModelPull: 'Ollama モデル {{ .name }} をプルします'

View file

@ -123,6 +123,8 @@ ErrBuildDirNotFound: '빌드 디렉토리가 존재하지 않습니다'
ErrImageNotExist: '운영 환경 {{ .name }} 이미지가 존재하지 않습니다. 운영 환경을 다시 편집하세요.'
ErrProxyIsUsed: "로드 밸런싱이 역방향 프록시에 의해 사용되었으므로 삭제할 수 없습니다"
ErrSSLValid: '인증서 파일에 문제가 있습니다. 인증서 상태를 확인하세요!'
ErrWebsiteDir: "웹사이트 디렉토리 내의 디렉토리를 선택하세요."
ErrComposerFileNotFound: "composer.json 파일이 존재하지 않습니다"
#SSL인증
ErrSSLCannotDelete: '{{ .name }} 인증서는 웹사이트에서 사용 중이므로 삭제할 수 없습니다.'
@ -328,6 +330,7 @@ SubTask: '하위 작업'
RuntimeExtension: '런타임 환경 확장'
TaskIsExecuting: '작업이 실행 중입니다'
CustomAppstore: '사용자 정의 애플리케이션 웨어하우스'
TaskExec": '실행'
# 작업 - ai
OllamaModelPull: 'Ollama 모델 {{ .name }}을(를) 끌어오세요'

View file

@ -122,6 +122,8 @@ ErrBuildDirNotFound: 'Direktori binaan tidak wujud'
ErrImageNotExist: 'Imej persekitaran operasi {{ .name }} tidak wujud, sila edit semula persekitaran pengendalian'
ErrProxyIsUsed: "Pengimbang beban telah digunakan oleh pengganti terbalik, tidak boleh dipadamkan"
ErrSSLValid: 'Fail sijil bermasalah, sila periksa status sijil!'
ErrWebsiteDir: "Sila pilih direktori dalam direktori laman web."
ErrComposerFileNotFound: "Fail composer.json tidak wujud"
#ssl
ErrSSLCannotDelete: 'Sijil {{ .name }} sedang digunakan oleh tapak web dan tidak boleh dipadamkan'
@ -327,6 +329,7 @@ SubTask: 'Subtugas'
RuntimeExtension: 'Sambungan Persekitaran Runtime'
TaskIsExecuting: 'Tugas sedang berjalan'
CustomAppstore: 'Gudang aplikasi tersuai'
TaskExec: 'Laksanakan'
# tugasan - ai
OllamaModelPull: 'Tarik model Ollama {{ .name }}'

View file

@ -123,6 +123,8 @@ ErrBuildDirNotFound: 'O diretório de compilação não existe'
ErrImageNotExist: 'A imagem do ambiente operacional {{ .name }} não existe, edite novamente o ambiente operacional'
ErrProxyIsUsed: "Balanceamento de carga foi usado por proxy reverso, não pode ser excluído"
ErrSSLValid: 'O arquivo do certificado está anormal, verifique o status do certificado!'
ErrWebsiteDir: "Por favor, selecione um diretório dentro do diretório do site."
ErrComposerFileNotFound: "O arquivo composer.json não existe"
#ssl
ErrSSLCannotDelete: 'O certificado {{ .name }} está sendo usado por um site e não pode ser excluído'
@ -328,6 +330,7 @@ SubTask: 'Subtarefa'
RuntimeExtension: 'Extensão do ambiente de tempo de execução'
TaskIsExecuting: 'A tarefa está em execução'
CustomAppstore: 'Armazém de aplicativos personalizados'
TaskExec: 'Executar'
# tarefa - ai
OllamaModelPull: 'Puxar modelo Ollama {{ .name }}'

View file

@ -123,6 +123,8 @@ ErrBuildDirNotFound: 'Каталог сборки не существует'
ErrImageNotExist: 'Образ операционной среды {{ .name }} не существует, пожалуйста, отредактируйте операционную среду заново'
ErrProxyIsUsed: "Балансировка нагрузки используется обратным прокси, невозможно удалить"
ErrSSLValid: 'Файл сертификата аномален, проверьте статус сертификата!'
ErrWebsiteDir: "Пожалуйста, выберите директорию внутри директории сайта."
ErrComposerFileNotFound: "Файл composer.json не существует"
#ssl
ErrSSLCannotDelete: 'Сертификат {{ .name }} используется веб-сайтом и не может быть удален'
@ -328,6 +330,7 @@ SubTask: 'Подзадача'
RuntimeExtension: 'Расширение среды выполнения'
TaskIsExecuting: 'Задача выполняется'
CustomAppstore: 'Хранилище пользовательских приложений'
TaskExec: 'Выполнить'
# задача - ай
OllamaModelPull: 'Вытянуть модель Ollama {{ .name }}'

View file

@ -123,6 +123,8 @@ ErrBuildDirNotFound: 'Yapı dizini mevcut değil'
ErrImageNotExist: 'İşletim ortamı {{ .name }} image mevcut değil, lütfen işletim ortamını yeniden düzenleyin'
ErrProxyIsUsed: "Yük dengeleme ters proxy tarafından kullanıldı, silinemez"
ErrSSLValid: 'Sertifika dosyası anormal, lütfen sertifika durumunu kontrol edin!'
ErrWebsiteDir: "Lütfen web sitesi dizini içindeki bir dizin seçin."
ErrComposerFileNotFound: "composer.json dosyası mevcut değil"
#ssl
ErrSSLCannotDelete: '{{ .name }} sertifikası bir web sitesi tarafından kullanılıyor ve silinemez'
@ -326,6 +328,7 @@ SubTask: 'Alt görev'
RuntimeExtension: 'Çalışma Ortamı Uzantısı'
TaskIsExecuting: 'Görev çalışıyor'
CustomAppstore: 'Özel uygulama deposu'
TaskExec: 'Çalıştır'
# task - ai
OllamaModelPull: 'Ollama modeli {{ .name }} çek'

View file

@ -122,6 +122,8 @@ ErrBuildDirNotFound: '建置目錄不存在'
ErrImageNotExist: '執行環境{{ .name }} 映像不存在,請重新編輯執行環境'
ErrProxyIsUsed: "負載均衡已被反向代理使用,無法刪除"
ErrSSLValid: '證書文件異常,請檢查證書狀態!'
ErrWebsiteDir: "請選擇網站目錄下的目錄"
ErrComposerFileNotFound: "composer.json 文件不存在"
#ssl
ErrSSLCannotDelete: '{{ .name }} 憑證正在被網站使用,無法刪除'
@ -327,6 +329,7 @@ SubTask: '子任務'
RuntimeExtension: '執行環境擴充'
TaskIsExecuting: '任務正在運作'
CustomAppstore: '自訂應用程式倉庫'
TaskExec: '執行'
# task - ai
OllamaModelPull: '拉取 Ollama 模型{{ .name }} '

View file

@ -122,6 +122,8 @@ ErrBuildDirNotFound: "构建目录不存在"
ErrImageNotExist: "运行环境 {{ .name }} 镜像不存在,请重新编辑运行环境"
ErrProxyIsUsed: "负载均衡已被反向代理使用,无法删除"
ErrSSLValid: '证书文件异常,请检查证书状态!'
ErrWebsiteDir: '请选择网站目录下的目录'
ErrComposerFileNotFound: 'composer.json 文件不存在'
#ssl
ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除"
@ -327,6 +329,7 @@ SubTask: "子任务"
RuntimeExtension: "运行环境扩展"
TaskIsExecuting: "任务正在运行"
CustomAppstore: "自定义应用仓库"
TaskExec: "执行"
# task - ai
OllamaModelPull: "拉取 Ollama 模型 {{ .name }} "

View file

@ -86,5 +86,7 @@ func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) {
websiteRouter.POST("/databases", baseApi.ChangeWebsiteDatabase)
websiteRouter.POST("/crosssite", baseApi.OperateCrossSiteAccess)
websiteRouter.POST("/exec/composer", baseApi.ExecComposer)
}
}

View file

@ -655,4 +655,14 @@ export namespace Website {
websiteID: number;
operation: string;
}
export interface ExecComposer {
websiteID: number;
command: string;
extCommand?: string;
mirror: string;
dir: string;
user: string;
taskID: string;
}
}

View file

@ -347,3 +347,7 @@ export const listCustomRewrite = () => {
export const operateCrossSiteAccess = (req: Website.CrossSiteAccessOp) => {
return http.post(`/websites/crosssite`, req);
};
export const execComposer = (req: Website.ExecComposer) => {
return http.post(`/websites/exec/composer`, req);
};

View file

@ -2523,6 +2523,12 @@ const message = {
'The time static resources are cached locally in the browser, reducing redundant requests. Users will use the local cache directly before it expires when refreshing the page.',
donotLinkeDB: 'Do Not Link Database',
toWebsiteDir: 'Enter Website Directory',
execParameters: 'Execution Parameters',
extCommand: 'Supplementary Command',
mirror: 'Mirror Source',
execUser: 'Executing User',
execDir: 'Execution Directory',
packagist: 'China Full Mirror',
},
php: {
short_open_tag: 'Short tag support',

View file

@ -2438,6 +2438,12 @@ const message = {
'静的リソースがブラウザのローカルにキャッシュされる時間冗長なリクエストを減らします有効期限前にユーザーがページをリフレッシュするとローカルキャッシュが直接使用されます',
donotLinkeDB: 'データベースをリンクしない',
toWebsiteDir: 'ウェブサイトディレクトリに入る',
execParameters: '実行パラメータ',
extCommand: '補足コマンド',
mirror: 'ミラーソース',
execUser: '実行ユーザー',
execDir: '実行ディレクトリ',
packagist: '中国フルミラー',
},
php: {
short_open_tag: '短いタグサポート',

View file

@ -2396,6 +2396,12 @@ const message = {
'정적 리소스가 브라우저 로컬에 캐시되는 시간, 중복 요청을 줄입니다. 유효기간 전에 사용자가 페이지를 새로 고치면 로컬 캐시가 직접 사용됩니다.',
donotLinkeDB: '데이터베이스 연결하지 않기',
toWebsiteDir: '웹사이트 디렉토리로 이동',
execParameters: '실행 매개변수',
extCommand: '추가 명령',
mirror: '미러 소스',
execUser: '실행 사용자',
execDir: '실행 디렉토리',
packagist: '중국 전체 미러',
},
php: {
short_open_tag: '짧은 태그 지원',

View file

@ -2495,6 +2495,12 @@ const message = {
'Masa sumber statik di-cache secara tempatan di pelayar, mengurangkan permintaan berulang. Pengguna akan menggunakan cache tempatan secara langsung sebelum tamat tempoh semasa menyegarkan halaman.',
donotLinkeDB: 'Jangan Sambungkan Pangkalan Data',
toWebsiteDir: 'Masuk ke Direktori Laman Web',
execParameters: 'Parameter Pelaksanaan',
extCommand: 'Arahan Tambahan',
mirror: 'Sumber Cermin',
execUser: 'Pengguna Melaksanakan',
execDir: 'Direktori Pelaksanaan',
packagist: 'Cermin Penuh China',
},
php: {
short_open_tag: 'Sokongan tag pendek',

View file

@ -2495,6 +2495,12 @@ const message = {
'O tempo que os recursos estáticos são armazenados em cache localmente no navegador, reduzindo requisições redundantes. Os usuários usarão o cache local diretamente antes de expirar ao atualizar a página.',
donotLinkeDB: 'Não Vincular Banco de Dados',
toWebsiteDir: 'Entrar no Diretório do Site',
execParameters: 'Parâmetros de Execução',
extCommand: 'Comando Suplementar',
mirror: 'Fonte de Espelho',
execUser: 'Usuário Executando',
execDir: 'Diretório de Execução',
packagist: 'Espelho Completo da China',
},
php: {
short_open_tag: 'Suporte para short tags',

View file

@ -2492,6 +2492,12 @@ const message = {
'Время, в течение которого статические ресурсы кешируются локально в браузере, уменьшая повторные запросы. Пользователи будут использовать локальный кеш напрямую, если срок его действия не истек при обновлении страницы.',
donotLinkeDB: 'Не связывать с базой данных',
toWebsiteDir: 'Перейти в каталог сайта',
execParameters: 'Параметры выполнения',
extCommand: 'Дополнительная команда',
mirror: 'Зеркальный источник',
execUser: 'Пользователь выполнения',
execDir: 'Каталог выполнения',
packagist: 'Полное зеркало Китая',
},
php: {
short_open_tag: 'Поддержка коротких тегов',

View file

@ -2554,6 +2554,12 @@ const message = {
'Statik kaynakların tarayıcıda yerel olarak önbelleğe alındığı süre, tekrarlayan istekleri azaltır. Kullanıcılar süre dolmadan önce sayfayı yenilediğinde yerel önbelleği doğrudan kullanır.',
donotLinkeDB: 'Veritabanına Bağlanma',
toWebsiteDir: 'Web Sitesi Dizinine Gir',
execParameters: 'Çalıştırma Parametreleri',
extCommand: 'Ek Komut',
mirror: 'Ayna Kaynağı',
execUser: 'Çalıştıran Kullanıcı',
execDir: 'Çalıştırma Dizini',
packagist: 'Çin Tam Aynası',
},
php: {
short_open_tag: 'Kısa etiket desteği',

View file

@ -2353,6 +2353,12 @@ const message = {
browserCacheTimeHelper: '靜態資源在瀏覽器本地緩存的時間減少重複請求到期前用戶刷新頁面會直接使用本地緩存',
donotLinkeDB: '不關聯數據庫',
toWebsiteDir: '進入網站目錄',
execParameters: '執行參數',
extCommand: '補充命令',
mirror: '鏡像源',
execUser: '執行用戶',
execDir: '執行目錄',
packagist: '中國全量鏡像',
},
php: {
short_open_tag: '短標簽支持',

View file

@ -2342,6 +2342,12 @@ const message = {
browserCacheTimeHelper: '静态资源在浏览器本地缓存的时间减少重复请求到期前用户刷新页面会直接使用本地缓存',
donotLinkeDB: '不关联数据库',
toWebsiteDir: '进入网站目录',
execParameters: '执行参数',
extCommand: '补充命令',
mirror: '镜像源',
execUser: '执行用户',
execDir: '执行目录',
packagist: '中国全量镜像',
},
php: {
short_open_tag: '短标签支持',

View file

@ -0,0 +1,127 @@
<template>
<el-form label-position="right" label-width="100px">
<el-form-item :label="$t('website.execParameters')">
<el-select v-model="req.command" class="p-w-400">
<el-option label="install" value="install"></el-option>
<el-option label="update" value="update"></el-option>
<el-option label="require" value="require"></el-option>
<el-option label="create-project" value="create-project"></el-option>
<el-option :label="$t('container.custom')" value="custom"></el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('website.extCommand')">
<el-input v-model.trim="req.extCommand" class="p-w-400"></el-input>
</el-form-item>
<el-form-item :label="$t('website.mirror')">
<el-select v-model="req.mirror" class="p-w-400">
<el-option
v-for="mirror in mirrors"
:key="mirror.label"
:value="mirror.value"
:label="mirror.label + ' [' + mirror.value + ']'"
></el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('website.execUser')">
<el-select v-model="req.user" class="p-w-400">
<el-option label="www-data" value="www-data"></el-option>
<el-option label="root" value="root"></el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('website.execDir')">
<el-input v-model.trim="req.dir" class="p-w-400">
<template #prepend>
<FileList :path="req.dir" @choose="getPath" :dir="true"></FileList>
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="exec">{{ $t('commons.button.handle') }}</el-button>
</el-form-item>
</el-form>
<TaskLog ref="taskLogRef" @close="search" />
</template>
<script setup lang="ts">
import { execComposer, getWebsite } from '@/api/modules/website';
import i18n from '@/lang';
import { newUUID } from '@/utils/util';
import TaskLog from '@/components/log/task/index.vue';
const props = defineProps({
websiteID: {
type: Number,
default: 0,
},
});
const req = reactive({
websiteID: 0,
command: 'install',
extCommand: '',
mirror: 'https://mirrors.aliyun.com/composer/',
dir: '',
user: 'www-data',
taskID: '',
});
const loading = ref(false);
const taskLogRef = ref();
const mirrors = [
{
label: i18n.global.t('runtime.aliyun') + '(mirrors.aliyun.com)',
value: 'https://mirrors.aliyun.com/composer/',
},
{
label: i18n.global.t('website.tencentCloud') + '(mirrors.cloud.tencent.com)',
value: 'https://mirrors.cloud.tencent.com/composer/',
},
{
label: i18n.global.t('commons.table.default') + '(repo.packagist.org)',
value: 'https://repo.packagist.org',
},
{
label: i18n.global.t('website.packagist') + '(packagist.phpcomposer.com)',
value: 'https://packagist.phpcomposer.com',
},
{
label: i18n.global.t('website.huaweicloud') + '(mirrors.huaweicloud.com)',
value: 'https://mirrors.huaweicloud.com/repository/php',
},
{
label: 'Packagist Mirror' + '(packagist.mirrors.sjtug.sjtu.edu.cn)',
value: 'https://packagist.mirrors.sjtug.sjtu.edu.cn/',
},
];
const getPath = (execDir: string) => {
req.dir = execDir;
};
const search = () => {
loading.value = true;
getWebsite(req.websiteID)
.then((res) => {
console.log(res.data);
req.dir = res.data.sitePath + '/index';
})
.finally(() => {
loading.value = false;
});
};
const exec = async () => {
const taskID = newUUID();
req.taskID = taskID;
try {
await execComposer(req);
taskLogRef.value.openWithTaskID(taskID);
} catch (error) {
return;
}
};
onMounted(() => {
req.websiteID = props.websiteID;
search();
});
</script>

View file

@ -1,10 +1,10 @@
<template>
<div v-loading="loading">
<el-row>
<el-col :xs="20" :sm="12" :md="10" :lg="10" :xl="8">
<el-form label-position="right" label-width="120px">
<el-form-item>
<el-text type="info" v-if="website.type === 'static'">
<el-tabs type="border-card" v-model="tabIndex">
<el-tab-pane :label="$t('website.changeVersion')">
<el-form label-position="right" label-width="100px" v-if="tabIndex == '0'">
<el-form-item v-if="website.type === 'static'">
<el-text type="info">
{{ $t('website.staticChangePHPHelper') }}
</el-text>
</el-form-item>
@ -32,13 +32,23 @@
</el-col>
</el-row>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane
:label="$t('website.openBaseDir')"
v-if="website.type === 'runtime' && website.runtimeType == 'php'"
>
<el-form label-position="right" label-width="100px" v-if="tabIndex == '1'">
<el-form-item :label="$t('website.openBaseDir')">
<el-switch v-model="openBaseDir" @change="operateCrossSite"></el-switch>
<span class="input-help">{{ $t('website.openBaseDirHelper') }}</span>
</el-form-item>
</el-form>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane :label="'Composer'" v-if="website.type === 'runtime' && website.runtimeType == 'php'">
<Composer :websiteID="id" v-if="tabIndex == '2'" />
</el-tab-pane>
</el-tabs>
</div>
</template>
@ -50,6 +60,7 @@ import { Website } from '@/api/interface/website';
import { changePHPVersion, getWebsite, operateCrossSiteAccess } from '@/api/modules/website';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import Composer from './composer/index.vue';
const props = defineProps({
id: {
type: Number,
@ -68,8 +79,10 @@ const oldRuntimeID = ref(0);
const website = ref({
type: '',
openBaseDir: false,
runtimeType: '',
});
const openBaseDir = ref(false);
const tabIndex = ref('0');
const getRuntimes = async () => {
try {

View file

@ -111,6 +111,8 @@ const initData = (id: number): Website.ProxyConfig => ({
proxyHost: '$host',
replaces: {},
proxySSLName: '',
serverCacheTime: 10,
serverCacheUnit: 'm',
});
const openCreate = () => {