mirror of
				https://github.com/1Panel-dev/1Panel.git
				synced 2025-10-31 19:26:02 +08:00 
			
		
		
		
	feat: add terminal for runtimes (#8758)
This commit is contained in:
		
							parent
							
								
									6d8d6f05f1
								
							
						
					
					
						commit
						41b2dd82f0
					
				
					 8 changed files with 188 additions and 1 deletions
				
			
		|  | @ -552,7 +552,7 @@ func operateSupervisorCtl(operate, name, group, includeDir, containerName string | |||
| 		err    error | ||||
| 	) | ||||
| 	if containerName != "" { | ||||
| 		cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second)) | ||||
| 		cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(30 * time.Second)) | ||||
| 		output, err = cmdMgr.RunWithStdoutBashCf("docker exec  %s supervisorctl %s", containerName, strings.Join(processNames, " ")) | ||||
| 	} else { | ||||
| 		var out []byte | ||||
|  |  | |||
							
								
								
									
										85
									
								
								frontend/src/views/website/runtime/components/terminal.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								frontend/src/views/website/runtime/components/terminal.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,85 @@ | |||
| <template> | ||||
|     <DrawerPro | ||||
|         v-model="terminalVisible" | ||||
|         :header="$t('menu.terminal')" | ||||
|         @close="handleClose" | ||||
|         :resource="title" | ||||
|         size="large" | ||||
|     > | ||||
|         <template #content> | ||||
|             <el-form ref="formRef" :model="form" label-position="top"> | ||||
|                 <el-button v-if="!terminalOpen" @click="initTerm(formRef)"> | ||||
|                     {{ $t('commons.button.conn') }} | ||||
|                 </el-button> | ||||
|                 <el-button v-else @click="onClose()">{{ $t('commons.button.disConn') }}</el-button> | ||||
|                 <Terminal | ||||
|                     style="height: calc(100vh - 200px); margin-top: 18px" | ||||
|                     ref="terminalRef" | ||||
|                     v-if="terminalOpen" | ||||
|                 ></Terminal> | ||||
|             </el-form> | ||||
|         </template> | ||||
|     </DrawerPro> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import { reactive, ref, nextTick } from 'vue'; | ||||
| import { ElForm, FormInstance } from 'element-plus'; | ||||
| import Terminal from '@/components/terminal/index.vue'; | ||||
| 
 | ||||
| const title = ref(); | ||||
| const terminalVisible = ref(false); | ||||
| const terminalOpen = ref(false); | ||||
| const form = reactive({ | ||||
|     isCustom: false, | ||||
|     command: '', | ||||
|     user: '', | ||||
|     containerID: '', | ||||
| }); | ||||
| const formRef = ref(); | ||||
| const terminalRef = ref<InstanceType<typeof Terminal> | null>(null); | ||||
| 
 | ||||
| interface DialogProps { | ||||
|     containerID: string; | ||||
|     container: string; | ||||
| } | ||||
| const acceptParams = async (params: DialogProps): Promise<void> => { | ||||
|     terminalVisible.value = true; | ||||
|     form.containerID = params.containerID; | ||||
|     title.value = params.container; | ||||
|     form.isCustom = false; | ||||
|     form.user = ''; | ||||
|     form.command = '/bin/bash'; | ||||
|     terminalOpen.value = false; | ||||
|     initTerm(formRef.value); | ||||
| }; | ||||
| 
 | ||||
| const initTerm = (formEl: FormInstance | undefined) => { | ||||
|     if (!formEl) return; | ||||
|     formEl.validate(async (valid) => { | ||||
|         if (!valid) return; | ||||
|         terminalOpen.value = true; | ||||
|         await nextTick(); | ||||
|         terminalRef.value!.acceptParams({ | ||||
|             endpoint: '/api/v2/containers/exec', | ||||
|             args: `source=container&containerid=${form.containerID}&user=${form.user}&command=${form.command}`, | ||||
|             error: '', | ||||
|             initCmd: '', | ||||
|         }); | ||||
|     }); | ||||
| }; | ||||
| 
 | ||||
| const onClose = () => { | ||||
|     terminalRef.value?.onClose(); | ||||
|     terminalOpen.value = false; | ||||
| }; | ||||
| 
 | ||||
| function handleClose() { | ||||
|     onClose(); | ||||
|     terminalVisible.value = false; | ||||
| } | ||||
| 
 | ||||
| defineExpose({ | ||||
|     acceptParams, | ||||
| }); | ||||
| </script> | ||||
|  | @ -78,6 +78,7 @@ | |||
|         <ComposeLogs ref="composeLogRef" /> | ||||
|         <PortJumpDialog ref="dialogPortJumpRef" /> | ||||
|         <AppResources ref="checkRef" @close="search" /> | ||||
|         <Terminal ref="terminalRef" /> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -98,6 +99,7 @@ import { ElMessageBox } from 'element-plus'; | |||
| import { GlobalStore } from '@/store'; | ||||
| import RuntimeStatus from '@/views/website/runtime/components/runtime-status.vue'; | ||||
| import PortJump from '@/views/website/runtime/components/port-jump.vue'; | ||||
| import Terminal from '@/views/website/runtime/components/terminal.vue'; | ||||
| import { disabledButton } from '@/utils/runtime'; | ||||
| 
 | ||||
| const loading = ref(false); | ||||
|  | @ -107,6 +109,7 @@ const deleteRef = ref(); | |||
| const dialogPortJumpRef = ref(); | ||||
| const composeLogRef = ref(); | ||||
| const checkRef = ref(); | ||||
| const terminalRef = ref(); | ||||
| 
 | ||||
| const globalStore = GlobalStore(); | ||||
| const mobile = computed(() => { | ||||
|  | @ -162,6 +165,15 @@ const buttons = [ | |||
|             return disabledButton(row, 'edit'); | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         label: i18n.global.t('menu.terminal'), | ||||
|         click: function (row: Runtime.Runtime) { | ||||
|             openTerminal(row); | ||||
|         }, | ||||
|         disabled: function (row: Runtime.Runtime) { | ||||
|             return disabledButton(row, 'config'); | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         label: i18n.global.t('commons.button.delete'), | ||||
|         click: function (row: Runtime.Runtime) { | ||||
|  | @ -207,6 +219,11 @@ const openDelete = (row: Runtime.Runtime) => { | |||
|     }); | ||||
| }; | ||||
| 
 | ||||
| const openTerminal = (row: Runtime.Runtime) => { | ||||
|     const container = row.params['CONTAINER_NAME']; | ||||
|     terminalRef.value.acceptParams({ containerID: container, container: container }); | ||||
| }; | ||||
| 
 | ||||
| const openLog = (row: any) => { | ||||
|     composeLogRef.value.acceptParams({ compose: row.path + '/docker-compose.yml', resource: row.name }); | ||||
| }; | ||||
|  |  | |||
|  | @ -78,6 +78,7 @@ | |||
|         <ComposeLogs ref="composeLogRef" /> | ||||
|         <PortJumpDialog ref="dialogPortJumpRef" /> | ||||
|         <AppResources ref="checkRef" @close="search" /> | ||||
|         <Terminal ref="terminalRef" /> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -97,6 +98,7 @@ import AppResources from '@/views/website/runtime/php/check/index.vue'; | |||
| import { ElMessageBox } from 'element-plus'; | ||||
| import RuntimeStatus from '@/views/website/runtime/components/runtime-status.vue'; | ||||
| import PortJump from '@/views/website/runtime/components/port-jump.vue'; | ||||
| import Terminal from '@/views/website/runtime/components/terminal.vue'; | ||||
| import { disabledButton } from '@/utils/runtime'; | ||||
| import { GlobalStore } from '@/store'; | ||||
| const globalStore = GlobalStore(); | ||||
|  | @ -111,6 +113,7 @@ const deleteRef = ref(); | |||
| const dialogPortJumpRef = ref(); | ||||
| const composeLogRef = ref(); | ||||
| const checkRef = ref(); | ||||
| const terminalRef = ref(); | ||||
| 
 | ||||
| const paginationConfig = reactive({ | ||||
|     cacheSizeKey: 'runtime-page-size', | ||||
|  | @ -161,6 +164,15 @@ const buttons = [ | |||
|             return disabledButton(row, 'edit'); | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         label: i18n.global.t('menu.terminal'), | ||||
|         click: function (row: Runtime.Runtime) { | ||||
|             openTerminal(row); | ||||
|         }, | ||||
|         disabled: function (row: Runtime.Runtime) { | ||||
|             return disabledButton(row, 'config'); | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         label: i18n.global.t('commons.button.delete'), | ||||
|         click: function (row: Runtime.Runtime) { | ||||
|  | @ -214,6 +226,11 @@ const goDashboard = async (port: any, protocol: string) => { | |||
|     dialogPortJumpRef.value.acceptParams({ port: port, protocol: protocol }); | ||||
| }; | ||||
| 
 | ||||
| const openTerminal = (row: Runtime.Runtime) => { | ||||
|     const container = row.params['CONTAINER_NAME']; | ||||
|     terminalRef.value.acceptParams({ containerID: container, container: container }); | ||||
| }; | ||||
| 
 | ||||
| const operateRuntime = async (operate: string, ID: number) => { | ||||
|     try { | ||||
|         const action = await ElMessageBox.confirm( | ||||
|  |  | |||
|  | @ -78,6 +78,7 @@ | |||
|         <ComposeLogs ref="composeLogRef" /> | ||||
|         <PortJumpDialog ref="dialogPortJumpRef" /> | ||||
|         <AppResources ref="checkRef" @close="search" /> | ||||
|         <Terminal ref="terminalRef" /> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -97,6 +98,7 @@ import AppResources from '@/views/website/runtime/php/check/index.vue'; | |||
| import { ElMessageBox } from 'element-plus'; | ||||
| import RuntimeStatus from '@/views/website/runtime/components/runtime-status.vue'; | ||||
| import PortJump from '@/views/website/runtime/components/port-jump.vue'; | ||||
| import Terminal from '@/views/website/runtime/components/terminal.vue'; | ||||
| import { disabledButton } from '@/utils/runtime'; | ||||
| import { GlobalStore } from '@/store'; | ||||
| const globalStore = GlobalStore(); | ||||
|  | @ -111,6 +113,7 @@ const deleteRef = ref(); | |||
| const dialogPortJumpRef = ref(); | ||||
| const composeLogRef = ref(); | ||||
| const checkRef = ref(); | ||||
| const terminalRef = ref(); | ||||
| 
 | ||||
| const paginationConfig = reactive({ | ||||
|     cacheSizeKey: 'runtime-page-size', | ||||
|  | @ -161,6 +164,15 @@ const buttons = [ | |||
|             return disabledButton(row, 'edit'); | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         label: i18n.global.t('menu.terminal'), | ||||
|         click: function (row: Runtime.Runtime) { | ||||
|             openTerminal(row); | ||||
|         }, | ||||
|         disabled: function (row: Runtime.Runtime) { | ||||
|             return disabledButton(row, 'config'); | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         label: i18n.global.t('commons.button.delete'), | ||||
|         click: function (row: Runtime.Runtime) { | ||||
|  | @ -210,6 +222,11 @@ const openLog = (row: any) => { | |||
|     composeLogRef.value.acceptParams({ compose: row.path + '/docker-compose.yml', resource: row.name }); | ||||
| }; | ||||
| 
 | ||||
| const openTerminal = (row: Runtime.Runtime) => { | ||||
|     const container = row.params['CONTAINER_NAME']; | ||||
|     terminalRef.value.acceptParams({ containerID: container, container: container }); | ||||
| }; | ||||
| 
 | ||||
| const goDashboard = async (port: any, protocol: string) => { | ||||
|     dialogPortJumpRef.value.acceptParams({ port: port, protocol: protocol }); | ||||
| }; | ||||
|  |  | |||
|  | @ -79,6 +79,7 @@ | |||
|         <PortJumpDialog ref="dialogPortJumpRef" /> | ||||
|         <Modules ref="moduleRef" /> | ||||
|         <AppResources ref="checkRef" @close="search" /> | ||||
|         <Terminal ref="terminalRef" /> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -99,6 +100,7 @@ import AppResources from '@/views/website/runtime/php/check/index.vue'; | |||
| import { ElMessageBox } from 'element-plus'; | ||||
| import RuntimeStatus from '@/views/website/runtime/components/runtime-status.vue'; | ||||
| import PortJump from '@/views/website/runtime/components/port-jump.vue'; | ||||
| import Terminal from '@/views/website/runtime/components/terminal.vue'; | ||||
| import { disabledButton } from '@/utils/runtime'; | ||||
| import { GlobalStore } from '@/store'; | ||||
| const globalStore = GlobalStore(); | ||||
|  | @ -114,6 +116,7 @@ const dialogPortJumpRef = ref(); | |||
| const composeLogRef = ref(); | ||||
| const moduleRef = ref(); | ||||
| const checkRef = ref(); | ||||
| const terminalRef = ref(); | ||||
| 
 | ||||
| const paginationConfig = reactive({ | ||||
|     cacheSizeKey: 'runtime-page-size', | ||||
|  | @ -173,6 +176,15 @@ const buttons = [ | |||
|             return disabledButton(row, 'edit'); | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         label: i18n.global.t('menu.terminal'), | ||||
|         click: function (row: Runtime.Runtime) { | ||||
|             openTerminal(row); | ||||
|         }, | ||||
|         disabled: function (row: Runtime.Runtime) { | ||||
|             return disabledButton(row, 'config'); | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         label: i18n.global.t('commons.button.delete'), | ||||
|         click: function (row: Runtime.Runtime) { | ||||
|  | @ -230,6 +242,11 @@ const goDashboard = async (port: any, protocol: string) => { | |||
|     dialogPortJumpRef.value.acceptParams({ port: port, protocol: protocol }); | ||||
| }; | ||||
| 
 | ||||
| const openTerminal = (row: Runtime.Runtime) => { | ||||
|     const container = row.params['CONTAINER_NAME']; | ||||
|     terminalRef.value.acceptParams({ containerID: container, container: container }); | ||||
| }; | ||||
| 
 | ||||
| const operateRuntime = async (operate: string, ID: number) => { | ||||
|     try { | ||||
|         const action = await ElMessageBox.confirm( | ||||
|  |  | |||
|  | @ -103,6 +103,7 @@ | |||
|         <ComposeLogs ref="composeLogRef" :highlightDiff="200" /> | ||||
|         <Config ref="configRef" /> | ||||
|         <Supervisor ref="supervisorRef" /> | ||||
|         <Terminal ref="terminalRef" /> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -125,6 +126,7 @@ import ComposeLogs from '@/components/log/compose/index.vue'; | |||
| import Config from '@/views/website/runtime/php/config/index.vue'; | ||||
| import Supervisor from '@/views/website/runtime/php/supervisor/index.vue'; | ||||
| import RuntimeStatus from '@/views/website/runtime/components/runtime-status.vue'; | ||||
| import Terminal from '@/views/website/runtime/components/terminal.vue'; | ||||
| import { disabledButton } from '@/utils/runtime'; | ||||
| import { GlobalStore } from '@/store'; | ||||
| const globalStore = GlobalStore(); | ||||
|  | @ -155,6 +157,7 @@ const items = ref<Runtime.RuntimeDTO[]>([]); | |||
| const composeLogRef = ref(); | ||||
| const configRef = ref(); | ||||
| const supervisorRef = ref(); | ||||
| const terminalRef = ref(); | ||||
| 
 | ||||
| const buttons = [ | ||||
|     { | ||||
|  | @ -220,6 +223,15 @@ const buttons = [ | |||
|             return disabledButton(row, 'config'); | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         label: i18n.global.t('menu.terminal'), | ||||
|         click: function (row: Runtime.Runtime) { | ||||
|             openTerminal(row); | ||||
|         }, | ||||
|         disabled: function (row: Runtime.Runtime) { | ||||
|             return disabledButton(row, 'config'); | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         label: i18n.global.t('commons.button.delete'), | ||||
|         click: function (row: Runtime.Runtime) { | ||||
|  | @ -258,6 +270,11 @@ const openSupervisor = (row: Runtime.Runtime) => { | |||
|     supervisorRef.value.acceptParams(row.id); | ||||
| }; | ||||
| 
 | ||||
| const openTerminal = (row: Runtime.Runtime) => { | ||||
|     const container = row.params['CONTAINER_NAME']; | ||||
|     terminalRef.value.acceptParams({ containerID: container, container: container }); | ||||
| }; | ||||
| 
 | ||||
| const openLog = (row: Runtime.RuntimeDTO) => { | ||||
|     if (row.status == 'Running') { | ||||
|         composeLogRef.value.acceptParams({ compose: row.path + '/docker-compose.yml', resource: row.name }); | ||||
|  |  | |||
|  | @ -78,6 +78,7 @@ | |||
|         <ComposeLogs ref="composeLogRef" /> | ||||
|         <PortJumpDialog ref="dialogPortJumpRef" /> | ||||
|         <AppResources ref="checkRef" @close="search" /> | ||||
|         <Terminal ref="terminalRef" /> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -97,6 +98,7 @@ import AppResources from '@/views/website/runtime/php/check/index.vue'; | |||
| import { ElMessageBox } from 'element-plus'; | ||||
| import RuntimeStatus from '@/views/website/runtime/components/runtime-status.vue'; | ||||
| import PortJump from '@/views/website/runtime/components/port-jump.vue'; | ||||
| import Terminal from '@/views/website/runtime/components/terminal.vue'; | ||||
| import { disabledButton } from '@/utils/runtime'; | ||||
| import { GlobalStore } from '@/store'; | ||||
| const globalStore = GlobalStore(); | ||||
|  | @ -111,6 +113,7 @@ const deleteRef = ref(); | |||
| const dialogPortJumpRef = ref(); | ||||
| const composeLogRef = ref(); | ||||
| const checkRef = ref(); | ||||
| const terminalRef = ref(); | ||||
| 
 | ||||
| const paginationConfig = reactive({ | ||||
|     cacheSizeKey: 'runtime-page-size', | ||||
|  | @ -161,6 +164,15 @@ const buttons = [ | |||
|             return disabledButton(row, 'edit'); | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         label: i18n.global.t('menu.terminal'), | ||||
|         click: function (row: Runtime.Runtime) { | ||||
|             openTerminal(row); | ||||
|         }, | ||||
|         disabled: function (row: Runtime.Runtime) { | ||||
|             return disabledButton(row, 'config'); | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         label: i18n.global.t('commons.button.delete'), | ||||
|         click: function (row: Runtime.Runtime) { | ||||
|  | @ -214,6 +226,11 @@ const goDashboard = async (port: any, protocol: string) => { | |||
|     dialogPortJumpRef.value.acceptParams({ port: port, protocol: protocol }); | ||||
| }; | ||||
| 
 | ||||
| const openTerminal = (row: Runtime.Runtime) => { | ||||
|     const container = row.params['CONTAINER_NAME']; | ||||
|     terminalRef.value.acceptParams({ containerID: container, container: container }); | ||||
| }; | ||||
| 
 | ||||
| const operateRuntime = async (operate: string, ID: number) => { | ||||
|     try { | ||||
|         const action = await ElMessageBox.confirm( | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue