mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-01-06 07:04:25 +08:00
feat: support batch operate website (#10415)
Refs https://github.com/1Panel-dev/1Panel/issues/5142
This commit is contained in:
parent
c216726449
commit
2040f8f3b0
29 changed files with 278 additions and 30 deletions
|
|
@ -1141,3 +1141,23 @@ func (b *BaseApi) ExecComposer(c *gin.Context) {
|
|||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website
|
||||
// @Summary Batch operate websites
|
||||
// @Accept json
|
||||
// @Param request body request.BatchOpWebsite true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/batch/operate [post]
|
||||
func (b *BaseApi) BatchOpWebsites(c *gin.Context) {
|
||||
var req request.BatchWebsiteOp
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := websiteService.BatchOpWebsite(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,12 @@ type WebsiteOp struct {
|
|||
Operate string `json:"operate"`
|
||||
}
|
||||
|
||||
type BatchWebsiteOp struct {
|
||||
IDs []uint `json:"ids" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required"`
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
}
|
||||
|
||||
type WebsiteRedirectUpdate struct {
|
||||
WebsiteID uint `json:"websiteId" validate:"required"`
|
||||
Key string `json:"key" validate:"required"`
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ type IWebsiteService interface {
|
|||
UpdateWebsite(req request.WebsiteUpdate) error
|
||||
DeleteWebsite(req request.WebsiteDelete) error
|
||||
GetWebsite(id uint) (response.WebsiteDTO, error)
|
||||
BatchOpWebsite(req request.BatchWebsiteOp) error
|
||||
|
||||
CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error)
|
||||
GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error)
|
||||
|
|
@ -517,6 +518,43 @@ func (w WebsiteService) OpWebsite(req request.WebsiteOp) error {
|
|||
return websiteRepo.Save(context.Background(), &website)
|
||||
}
|
||||
|
||||
func (w WebsiteService) BatchOpWebsite(req request.BatchWebsiteOp) error {
|
||||
websites, _ := websiteRepo.List(repo.WithByIDs(req.IDs))
|
||||
opTask, err := task.NewTaskWithOps(i18n.GetMsgByKey("Status"), task.TaskBatch, task.TaskScopeWebsite, req.TaskID, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opWebsiteTask := func(t *task.Task) error {
|
||||
for _, web := range websites {
|
||||
msg := fmt.Sprintf("%s %s", i18n.GetMsgByKey(req.Operate), web.PrimaryDomain)
|
||||
switch req.Operate {
|
||||
case constant.StopWeb, constant.StartWeb:
|
||||
if err := opWebsite(&web, req.Operate); err != nil {
|
||||
t.LogFailedWithErr(msg, err)
|
||||
continue
|
||||
}
|
||||
_ = websiteRepo.Save(context.Background(), &web)
|
||||
case "delete":
|
||||
if err := w.DeleteWebsite(request.WebsiteDelete{
|
||||
ID: web.ID,
|
||||
}); err != nil {
|
||||
t.LogFailedWithErr(msg, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
t.LogSuccess(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
opTask.AddSubTask("", opWebsiteTask, nil)
|
||||
|
||||
go func() {
|
||||
_ = opTask.Execute()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) GetWebsiteOptions(req request.WebsiteOptionReq) ([]response.WebsiteOption, error) {
|
||||
var options []repo.DBOption
|
||||
if len(req.Types) > 0 {
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ const (
|
|||
TaskHandle = "TaskHandle"
|
||||
TaskScan = "TaskScan"
|
||||
TaskExec = "TaskExec"
|
||||
TaskBatch = "TaskBatch"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -317,7 +318,7 @@ func (t *Task) LogFailedWithErr(msg string, err error) {
|
|||
}
|
||||
|
||||
func (t *Task) LogSuccess(msg string) {
|
||||
t.Logger.Println(msg + i18n.GetMsgByKey("Success"))
|
||||
t.Logger.Println(msg + " " + i18n.GetMsgByKey("Success"))
|
||||
}
|
||||
func (t *Task) LogSuccessF(format string, v ...any) {
|
||||
t.Logger.Println(fmt.Sprintf(format, v...) + i18n.GetMsgByKey("Success"))
|
||||
|
|
|
|||
|
|
@ -140,6 +140,10 @@ 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"
|
||||
ErrRuntimeNoPort: "The runtime environment is not set with a port, please edit the runtime environment first."
|
||||
Status: 'Status'
|
||||
start: 'Start'
|
||||
stop: 'Stop'
|
||||
delete: 'Delete'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: 'The {{ .name }} certificate is being used by a website and cannot be deleted'
|
||||
|
|
@ -353,6 +357,7 @@ RuntimeExtension: 'Runtime Environment Extension'
|
|||
TaskIsExecuting: 'Task is running'
|
||||
CustomAppstore: 'Custom application warehouse'
|
||||
TaskExec: 'Execute'
|
||||
TaskBatch: "Batch Operation"
|
||||
|
||||
# task - clam
|
||||
Clamscan: "Scan {{ .name }}"
|
||||
|
|
|
|||
|
|
@ -138,6 +138,10 @@ ErrSSLValid: 'Archivo de certificado anómalo, ¡revise el estado del certificad
|
|||
ErrWebsiteDir: "Por favor seleccione un directorio dentro del directorio del sitio web"
|
||||
ErrComposerFileNotFound: "El archivo composer.json no existe"
|
||||
ErrRuntimeNoPort: "El entorno de ejecución no tiene configurado un puerto, edítelo primero"
|
||||
Status: 'Estado'
|
||||
start: 'Iniciar'
|
||||
stop: 'Detener'
|
||||
delete: 'Eliminar'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: 'El certificado {{ .name }} está siendo utilizado por un sitio web y no puede eliminarse'
|
||||
|
|
@ -158,7 +162,6 @@ ApplySSLFailed: 'Solicitud de certificado para [{{ .domain }}] fallida, {{ .deta
|
|||
ApplySSLSuccess: '¡Solicitud de certificado para [{{ .domain }}] exitosa!'
|
||||
DNSAccountName: 'Cuenta DNS [{{ .name }}] proveedor [{{ .type }}]'
|
||||
PushDirLog: 'Certificado empujado al directorio [{{ .path }}] {{ .status }}'
|
||||
ErrDeleteCAWithSSL: 'La organización actual tiene un certificado emitido y no puede eliminarse.'
|
||||
ErrDeleteWithPanelSSL: 'La configuración SSL del panel utiliza este certificado y no puede eliminarse'
|
||||
ErrDefaultCA: 'La autoridad por defecto no puede eliminarse'
|
||||
ApplyWebSiteSSLLog: 'Iniciando renovación del certificado del sitio web {{ .name }}'
|
||||
|
|
@ -352,6 +355,7 @@ TaskIsExecuting: 'Tarea en ejecución'
|
|||
CustomAppstore: 'Almacén de aplicaciones personalizado'
|
||||
TaskClean: "Limpieza"
|
||||
TaskExec: "Ejecutar"
|
||||
TaskBatch: "Operación por Lotes"
|
||||
|
||||
# task - ai
|
||||
OllamaModelPull: 'Descargar modelo Ollama {{ .name }}'
|
||||
|
|
@ -428,12 +432,9 @@ ImageBuild: 'Construcción de imagen'
|
|||
ImageBuildStdoutCheck: 'Analizar salida de la imagen'
|
||||
ImageBuildRes: 'Salida de construcción de imagen: {{ .name }}'
|
||||
ImagePull: 'Descargar imagen'
|
||||
ImageRepoAuthFromDB: 'Obtener autenticación de repositorio desde base de datos'
|
||||
ImaegPullRes: 'Salida de descarga de imagen: {{ .name }}'
|
||||
ImagePush: 'Subir imagen'
|
||||
ImageRenameTag: 'Modificar etiqueta de imagen'
|
||||
ImageNewTag: 'Nueva etiqueta de imagen {{ .name }}'
|
||||
ImaegPushRes: 'Salida de subida de imagen: {{ .name }}'
|
||||
ComposeCreate: 'Crear orquestación'
|
||||
ComposeCreateRes: 'Salida de creación de orquestación: {{ .name }}'
|
||||
ImageRepoAuthFromDB: "Obtener información de autenticación del repositorio desde la base de datos"
|
||||
|
|
|
|||
|
|
@ -140,6 +140,10 @@ ErrSSLValid: '証明書ファイルが異常です、証明書の状態を確認
|
|||
ErrWebsiteDir: "ウェブサイトディレクトリ内のディレクトリを選択してください。"
|
||||
ErrComposerFileNotFound: "composer.json ファイルが存在しません"
|
||||
ErrRuntimeNoPort: "ランタイム環境にポートが設定されていません。先にランタイム環境を編集してください。"
|
||||
Status: 'ステータス'
|
||||
start: '開始'
|
||||
stop: '停止'
|
||||
delete: '削除'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: '{{ .name }} 証明書は Web サイトで使用されているため、削除できません'
|
||||
|
|
@ -353,6 +357,7 @@ RuntimeExtension: 'ランタイム環境拡張'
|
|||
TaskIsExecuting: 'タスクは実行中です'
|
||||
CustomAppstore: 'カスタム アプリケーション ウェアハウス'
|
||||
TaskExec: '実行'
|
||||
TaskBatch: "一括操作"
|
||||
|
||||
# task - clam
|
||||
Clamscan: "{{ .name }} をスキャン"
|
||||
|
|
|
|||
|
|
@ -140,6 +140,10 @@ ErrSSLValid: '인증서 파일에 문제가 있습니다. 인증서 상태를
|
|||
ErrWebsiteDir: "웹사이트 디렉토리 내의 디렉토리를 선택하세요."
|
||||
ErrComposerFileNotFound: "composer.json 파일이 존재하지 않습니다"
|
||||
ErrRuntimeNoPort: "런타임 환경에 포트가 설정되지 않았습니다. 먼저 런타임 환경을 편집하세요."
|
||||
Status: '상태'
|
||||
start: '시작'
|
||||
stop: '중지'
|
||||
delete: '삭제'
|
||||
|
||||
#SSL인증
|
||||
ErrSSLCannotDelete: '{{ .name }} 인증서는 웹사이트에서 사용 중이므로 삭제할 수 없습니다.'
|
||||
|
|
@ -353,6 +357,7 @@ RuntimeExtension: '런타임 환경 확장'
|
|||
TaskIsExecuting: '작업이 실행 중입니다'
|
||||
CustomAppstore: '사용자 정의 애플리케이션 웨어하우스'
|
||||
TaskExec: '실행'
|
||||
TaskBatch: "일괄 작업"
|
||||
|
||||
# task - clam
|
||||
Clamscan: "{{ .name }} 스캔"
|
||||
|
|
|
|||
|
|
@ -143,6 +143,10 @@ ErrSSLValid: 'Fail sijil bermasalah, sila periksa status sijil!'
|
|||
ErrWebsiteDir: "Sila pilih direktori dalam direktori laman web."
|
||||
ErrComposerFileNotFound: "Fail composer.json tidak wujud"
|
||||
ErrRuntimeNoPort: "Persekitaran runtime tidak diset dengan port, sila edit persekitaran runtime terlebih dahulu."
|
||||
Status: 'Status'
|
||||
start: 'Mulakan'
|
||||
stop: 'Berhenti'
|
||||
delete: 'Padam'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: 'Sijil {{ .name }} sedang digunakan oleh tapak web dan tidak boleh dipadamkan'
|
||||
|
|
@ -353,6 +357,7 @@ RuntimeExtension: 'Sambungan Persekitaran Runtime'
|
|||
TaskIsExecuting: 'Tugas sedang berjalan'
|
||||
CustomAppstore: 'Gudang aplikasi tersuai'
|
||||
TaskExec: 'Laksanakan'
|
||||
TaskBatch: "Operasi Batch"
|
||||
|
||||
# task - clam
|
||||
Clamscan: "Imbas {{ .name }}"
|
||||
|
|
|
|||
|
|
@ -143,6 +143,10 @@ ErrSSLValid: 'O arquivo do certificado está anormal, verifique o status do cert
|
|||
ErrWebsiteDir: "Por favor, selecione um diretório dentro do diretório do site."
|
||||
ErrComposerFileNotFound: "O arquivo composer.json não existe"
|
||||
ErrRuntimeNoPort: "O ambiente de tempo de execução não está configurado com uma porta, edite o ambiente de tempo de execução primeiro."
|
||||
Status: 'Status'
|
||||
start: 'Iniciar'
|
||||
stop: 'Parar'
|
||||
delete: 'Excluir'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: 'O certificado {{ .name }} está sendo usado por um site e não pode ser excluído'
|
||||
|
|
@ -353,6 +357,7 @@ 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'
|
||||
TaskBatch: "Operação em Lote"
|
||||
|
||||
# task - clam
|
||||
Clamscan: "Escanear {{ .name }}"
|
||||
|
|
|
|||
|
|
@ -143,6 +143,10 @@ ErrSSLValid: 'Файл сертификата аномален, проверьт
|
|||
ErrWebsiteDir: "Пожалуйста, выберите директорию внутри директории сайта."
|
||||
ErrComposerFileNotFound: "Файл composer.json не существует"
|
||||
ErrRuntimeNoPort: "Среда выполнения не настроена с портом, сначала отредактируйте среду выполнения."
|
||||
Status: 'Статус'
|
||||
start: 'Запустить'
|
||||
stop: 'Остановить'
|
||||
delete: 'Удалить'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: 'Сертификат {{ .name }} используется веб-сайтом и не может быть удален'
|
||||
|
|
@ -353,6 +357,7 @@ RuntimeExtension: 'Расширение среды выполнения'
|
|||
TaskIsExecuting: 'Задача выполняется'
|
||||
CustomAppstore: 'Хранилище пользовательских приложений'
|
||||
TaskExec: 'Выполнить'
|
||||
TaskBatch: "Пакетная операция"
|
||||
|
||||
# task - clam
|
||||
Clamscan: "Сканировать {{ .name }}"
|
||||
|
|
|
|||
|
|
@ -144,6 +144,10 @@ ErrSSLValid: 'Sertifika dosyası anormal, lütfen sertifika durumunu kontrol edi
|
|||
ErrWebsiteDir: "Lütfen web sitesi dizini içindeki bir dizin seçin."
|
||||
ErrComposerFileNotFound: "composer.json dosyası mevcut değil"
|
||||
ErrRuntimeNoPort: "Çalışma zamanı ortamı bir porta sahip değil, lütfen önce çalışma zamanı ortamını düzenleyin."
|
||||
Status: 'Durum'
|
||||
start: 'Başlat'
|
||||
stop: 'Durdur'
|
||||
delete: 'Sil'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: '{{ .name }} sertifikası bir web sitesi tarafından kullanılıyor ve silinemez'
|
||||
|
|
@ -354,6 +358,7 @@ RuntimeExtension: 'Çalışma Ortamı Uzantısı'
|
|||
TaskIsExecuting: 'Görev çalışıyor'
|
||||
CustomAppstore: 'Özel uygulama deposu'
|
||||
TaskExec: 'Çalıştır'
|
||||
TaskBatch: "Toplu İşlem"
|
||||
|
||||
# task - clam
|
||||
Clamscan: "{{ .name }} Tara"
|
||||
|
|
|
|||
|
|
@ -139,6 +139,10 @@ ErrSSLValid: '證書文件異常,請檢查證書狀態!'
|
|||
ErrWebsiteDir: "請選擇網站目錄下的目錄"
|
||||
ErrComposerFileNotFound: "composer.json 文件不存在"
|
||||
ErrRuntimeNoPort: "執行環境未設定埠,請先編輯執行環境"
|
||||
Status: '狀態'
|
||||
start: '開啟'
|
||||
stop: '關閉'
|
||||
delete: '刪除'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: '{{ .name }} 憑證正在被網站使用,無法刪除'
|
||||
|
|
@ -352,6 +356,7 @@ RuntimeExtension: '執行環境擴充'
|
|||
TaskIsExecuting: '任務正在運作'
|
||||
CustomAppstore: '自訂應用程式倉庫'
|
||||
TaskExec: '執行'
|
||||
TaskBatch: "批量操作"
|
||||
|
||||
# task - clam
|
||||
Clamscan: "掃描 {{ .name }}"
|
||||
|
|
|
|||
|
|
@ -139,6 +139,10 @@ ErrSSLValid: '证书文件异常,请检查证书状态!'
|
|||
ErrWebsiteDir: '请选择网站目录下的目录'
|
||||
ErrComposerFileNotFound: 'composer.json 文件不存在'
|
||||
ErrRuntimeNoPort: '运行环境未设置端口,请先编辑运行环境'
|
||||
Status: '状态'
|
||||
start: '开启'
|
||||
stop: '关闭'
|
||||
delete: '删除'
|
||||
|
||||
#ssl
|
||||
ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除"
|
||||
|
|
@ -353,6 +357,7 @@ RuntimeExtension: "运行环境扩展"
|
|||
TaskIsExecuting: "任务正在运行"
|
||||
CustomAppstore: "自定义应用仓库"
|
||||
TaskExec: "执行"
|
||||
TaskBatch: "批量操作"
|
||||
|
||||
# task - clam
|
||||
Clamscan: "扫描 {{ .name }}"
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) {
|
|||
websiteRouter.POST("/del", baseApi.DeleteWebsite)
|
||||
websiteRouter.POST("/default/server", baseApi.ChangeDefaultServer)
|
||||
websiteRouter.POST("/group/change", baseApi.ChangeWebsiteGroup)
|
||||
websiteRouter.POST("/batch/operate", baseApi.BatchOpWebsites)
|
||||
|
||||
websiteRouter.GET("/domains/:websiteId", baseApi.GetWebDomains)
|
||||
websiteRouter.POST("/domains/del", baseApi.DeleteWebDomain)
|
||||
|
|
|
|||
|
|
@ -667,4 +667,10 @@ export namespace Website {
|
|||
user: string;
|
||||
taskID: string;
|
||||
}
|
||||
|
||||
export interface BatchOperate {
|
||||
ids: number[];
|
||||
operate: string;
|
||||
taskID: string;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -351,3 +351,7 @@ export const operateCrossSiteAccess = (req: Website.CrossSiteAccessOp) => {
|
|||
export const execComposer = (req: Website.ExecComposer) => {
|
||||
return http.post(`/websites/exec/composer`, req);
|
||||
};
|
||||
|
||||
export const batchOpreate = (req: Website.BatchOperate) => {
|
||||
return http.post(`/websites/batch/operate`, req);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,31 +22,38 @@
|
|||
</template>
|
||||
</fu-table>
|
||||
</div>
|
||||
<div class="table-footer-container">
|
||||
<div class="footer-left" v-if="slots.footerLeft">
|
||||
<el-checkbox v-model="leftSelect" @change="toggleSelection"></el-checkbox>
|
||||
<div class="ml-4">
|
||||
<slot name="footerLeft"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="complex-table__pagination flex items-center w-full sm:flex-row flex-col text-xs sm:text-sm"
|
||||
v-if="props.paginationConfig"
|
||||
:class="{ '!justify-between': slots.paginationLeft, '!justify-end': !slots.paginationLeft }"
|
||||
>
|
||||
<slot name="paginationLeft"></slot>
|
||||
<slot name="pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="paginationConfig.currentPage"
|
||||
v-model:page-size="paginationConfig.pageSize"
|
||||
:total="paginationConfig.total"
|
||||
:page-sizes="[5, 10, 20, 50, 100, 200, 500]"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
:size="mobile || paginationConfig.small ? 'small' : 'default'"
|
||||
:layout="
|
||||
mobile || paginationConfig.small
|
||||
? 'total, prev, pager, next'
|
||||
: 'total, sizes, prev, pager, next, jumper'
|
||||
"
|
||||
/>
|
||||
</slot>
|
||||
<div
|
||||
class="complex-table__pagination flex items-center w-full sm:flex-row flex-col text-xs sm:text-sm"
|
||||
v-if="props.paginationConfig"
|
||||
:class="{ '!justify-between': slots.paginationLeft, '!justify-end': !slots.paginationLeft }"
|
||||
>
|
||||
<slot name="paginationLeft"></slot>
|
||||
<slot name="pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="paginationConfig.currentPage"
|
||||
v-model:page-size="paginationConfig.pageSize"
|
||||
:total="paginationConfig.total"
|
||||
:page-sizes="[5, 10, 20, 50, 100, 200, 500]"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
:size="mobile || paginationConfig.small ? 'small' : 'default'"
|
||||
:layout="
|
||||
mobile || paginationConfig.small
|
||||
? 'total, prev, pager, next'
|
||||
: 'total, sizes, prev, pager, next, jumper'
|
||||
"
|
||||
/>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
v-if="rightClick.visible"
|
||||
class="context-menu"
|
||||
|
|
@ -104,6 +111,7 @@ const mobile = computed(() => {
|
|||
const tableRef = ref();
|
||||
const tableHeight = ref(0);
|
||||
const menuRef = ref<HTMLElement | null>(null);
|
||||
const leftSelect = ref(false);
|
||||
|
||||
const rightClick = ref({
|
||||
visible: false,
|
||||
|
|
@ -153,6 +161,11 @@ function sizeChange() {
|
|||
|
||||
function handleSelectionChange(row: any) {
|
||||
emit('update:selects', row);
|
||||
if (row.length > 0) {
|
||||
leftSelect.value = true;
|
||||
} else {
|
||||
leftSelect.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function sort(prop: string, order: string) {
|
||||
|
|
@ -236,6 +249,10 @@ function calcHeight() {
|
|||
}
|
||||
}
|
||||
|
||||
const toggleSelection = () => {
|
||||
tableRef.value.refElTable.toggleAllSelection();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
calcHeight();
|
||||
window.addEventListener('resize', calcHeight);
|
||||
|
|
@ -306,4 +323,26 @@ onBeforeUnmount(() => {
|
|||
.context-menu li.divided {
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
}
|
||||
.table-footer-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.footer-left {
|
||||
flex-shrink: 0;
|
||||
margin-right: 16px;
|
||||
margin-left: 12px;
|
||||
display: flex;
|
||||
|
||||
.footer-left-button {
|
||||
margin-left: 17px;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.complex-table__pagination {
|
||||
flex: 1;
|
||||
@include flex-row(flex-end);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -2583,6 +2583,9 @@ const message = {
|
|||
execUser: 'Executing User',
|
||||
execDir: 'Execution Directory',
|
||||
packagist: 'China Full Mirror',
|
||||
|
||||
batchOpreate: 'Batch Operation',
|
||||
batchOpreateHelper: 'Batch {0} websites, continue operation?',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Short tag support',
|
||||
|
|
|
|||
|
|
@ -2552,6 +2552,9 @@ const message = {
|
|||
execUser: 'Usuario de ejecución',
|
||||
execDir: 'Directorio de ejecución',
|
||||
packagist: 'Mirror China completo',
|
||||
|
||||
batchOpreate: 'Operación en Lote',
|
||||
batchOpreateHelper: 'Lote {0} sitios web, ¿continuar operación?',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Soporte de etiquetas cortas',
|
||||
|
|
|
|||
|
|
@ -2498,6 +2498,9 @@ const message = {
|
|||
execUser: '実行ユーザー',
|
||||
execDir: '実行ディレクトリ',
|
||||
packagist: '中国フルミラー',
|
||||
|
||||
batchOpreate: 'バッチ操作',
|
||||
batchOpreateHelper: 'ウェブサイトをバッチ{0}しますか?',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '短いタグサポート',
|
||||
|
|
|
|||
|
|
@ -2456,6 +2456,9 @@ const message = {
|
|||
execUser: '실행 사용자',
|
||||
execDir: '실행 디렉토리',
|
||||
packagist: '중국 전체 미러',
|
||||
|
||||
batchOpreate: '일괄 작업',
|
||||
batchOpreateHelper: '웹사이트를 일괄 {0}, 계속 작업하시겠습니까?',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '짧은 태그 지원',
|
||||
|
|
|
|||
|
|
@ -2554,6 +2554,9 @@ const message = {
|
|||
execUser: 'Pengguna Melaksanakan',
|
||||
execDir: 'Direktori Pelaksanaan',
|
||||
packagist: 'Cermin Penuh China',
|
||||
|
||||
batchOpreate: 'Operasi Pukal',
|
||||
batchOpreateHelper: 'Pukal {0} laman web, teruskan operasi?',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Sokongan tag pendek',
|
||||
|
|
|
|||
|
|
@ -2555,6 +2555,9 @@ const message = {
|
|||
execUser: 'Usuário Executando',
|
||||
execDir: 'Diretório de Execução',
|
||||
packagist: 'Espelho Completo da China',
|
||||
|
||||
batchOpreate: 'Operação em Lote',
|
||||
batchOpreateHelper: 'Lote {0} sites, continuar operação?',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Suporte para short tags',
|
||||
|
|
|
|||
|
|
@ -2553,6 +2553,9 @@ const message = {
|
|||
execUser: 'Пользователь выполнения',
|
||||
execDir: 'Каталог выполнения',
|
||||
packagist: 'Полное зеркало Китая',
|
||||
|
||||
batchOpreate: 'Пакетная операция',
|
||||
batchOpreateHelper: 'Пакетное {0} веб-сайтов, продолжить операцию?',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Поддержка коротких тегов',
|
||||
|
|
|
|||
|
|
@ -2612,6 +2612,9 @@ const message = {
|
|||
execUser: 'Çalıştıran Kullanıcı',
|
||||
execDir: 'Çalıştırma Dizini',
|
||||
packagist: 'Çin Tam Aynası',
|
||||
|
||||
batchOpreate: 'Toplu İşlem',
|
||||
batchOpreateHelper: 'Toplu {0} web siteleri, işlemi devam ettir?',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: 'Kısa etiket desteği',
|
||||
|
|
|
|||
|
|
@ -2406,6 +2406,9 @@ const message = {
|
|||
execUser: '執行使用者',
|
||||
execDir: '執行目錄',
|
||||
packagist: '中國全量鏡像',
|
||||
|
||||
batchOpreate: '批次操作',
|
||||
batchOpreateHelper: '批次{0}網站,是否繼續操作?',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '短標籤支援',
|
||||
|
|
|
|||
|
|
@ -2397,6 +2397,9 @@ const message = {
|
|||
execUser: '执行用户',
|
||||
execDir: '执行目录',
|
||||
packagist: '中国全量镜像',
|
||||
|
||||
batchOpreate: '批量操作',
|
||||
batchOpreateHelper: '批量{0}网站,是否继续操作?',
|
||||
},
|
||||
php: {
|
||||
short_open_tag: '短标签支持',
|
||||
|
|
|
|||
|
|
@ -77,7 +77,9 @@
|
|||
@cell-mouse-enter="showFavorite"
|
||||
@cell-mouse-leave="hideFavorite"
|
||||
localKey="websiteColumn"
|
||||
v-model:selects="selects"
|
||||
>
|
||||
<el-table-column type="selection" width="30" />
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
fix
|
||||
|
|
@ -264,6 +266,33 @@
|
|||
:fixed="mobile ? false : 'right'"
|
||||
fix
|
||||
/>
|
||||
<template #footerLeft>
|
||||
<div class="footer-left-button">
|
||||
<el-select class="p-w-200" v-model="batchReq.operate">
|
||||
<el-option
|
||||
:label="$t('commons.button.start') + $t('menu.website')"
|
||||
value="start"
|
||||
></el-option>
|
||||
<el-option
|
||||
:label="$t('commons.button.stop') + $t('menu.website')"
|
||||
value="stop"
|
||||
></el-option>
|
||||
<el-option
|
||||
:label="$t('commons.button.delete') + $t('menu.website')"
|
||||
value="delete"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<el-button
|
||||
class="ml-2"
|
||||
type="primary"
|
||||
:disabled="selects.length == 0 || batchReq.operate == ''"
|
||||
@click="batchOp"
|
||||
>
|
||||
{{ $t('website.batchOpreate') }}
|
||||
<span class="ml-1" v-if="selects.length > 0">({{ selects.length }})</span>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</ComplexTable>
|
||||
<el-card width="30%" v-if="disabledConfig && maskShow" class="mask-prompt">
|
||||
<span v-if="nginxIsExist">
|
||||
|
|
@ -289,6 +318,7 @@
|
|||
<GroupDialog @search="listGroup" ref="groupRef" />
|
||||
<NginxConfig v-if="openNginxConfig" v-loading="loading" :containerName="containerName" :status="nginxStatus" />
|
||||
<DefaultHtml ref="defaultHtmlRef" />
|
||||
<TaskLog ref="taskLogRef" @close="search" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -302,13 +332,14 @@ import DeleteWebsite from '@/views/website/website/delete/index.vue';
|
|||
import NginxConfig from '@/views/website/website/nginx/index.vue';
|
||||
import GroupDialog from '@/components/agent-group/index.vue';
|
||||
import AppStatus from '@/components/app-status/index.vue';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { onMounted, reactive, ref, computed } from 'vue';
|
||||
import { listDomains, opWebsite, searchWebsites, updateWebsite } from '@/api/modules/website';
|
||||
import { batchOpreate, listDomains, opWebsite, searchWebsites, updateWebsite } from '@/api/modules/website';
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { App } from '@/api/interface/app';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { dateFormatSimple } from '@/utils/util';
|
||||
import { dateFormatSimple, newUUID } from '@/utils/util';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { getAgentGroupList } from '@/api/modules/group';
|
||||
|
|
@ -356,6 +387,13 @@ const domains = ref<Website.Domain[]>([]);
|
|||
const columns = ref([]);
|
||||
const hoveredRowIndex = ref(-1);
|
||||
const websiteDir = ref();
|
||||
const selects = ref([]);
|
||||
const batchReq = reactive({
|
||||
operate: '',
|
||||
ids: [] as number[],
|
||||
taskID: '',
|
||||
});
|
||||
const taskLogRef = ref();
|
||||
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'website-page-size',
|
||||
|
|
@ -640,6 +678,25 @@ const updateRemark = (row: Website.Website, bulr: Function) => {
|
|||
updateWebsitConfig(row);
|
||||
};
|
||||
|
||||
const batchOp = () => {
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('website.batchOpreateHelper', [i18n.global.t('commons.button.' + batchReq.operate)]),
|
||||
i18n.global.t('website.batchOpreate'),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
},
|
||||
).then(async () => {
|
||||
batchReq.ids = selects.value.map((item) => item.id);
|
||||
const taskID = newUUID();
|
||||
batchReq.taskID = taskID;
|
||||
await batchOpreate(batchReq);
|
||||
taskLogRef.value.openWithTaskID(taskID);
|
||||
selects.value = [];
|
||||
batchReq.operate = '';
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
listGroup();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue