mirror of
				https://github.com/1Panel-dev/1Panel.git
				synced 2025-10-31 19:26:02 +08:00 
			
		
		
		
	feat: 增加终端自定义配置 (#6208)
This commit is contained in:
		
							parent
							
								
									12b7f806f9
								
							
						
					
					
						commit
						cfe4fa7a92
					
				
					 19 changed files with 533 additions and 28 deletions
				
			
		|  | @ -29,6 +29,21 @@ func (b *BaseApi) GetSettingInfo(c *gin.Context) { | |||
| 	helper.SuccessWithData(c, setting) | ||||
| } | ||||
| 
 | ||||
| // @Tags System Setting | ||||
| // @Summary Load system terminal setting info | ||||
| // @Description 加载系统终端配置信息 | ||||
| // @Success 200 {object} dto.TerminalInfo | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /core/settings/terminal/search [post] | ||||
| func (b *BaseApi) GetTerminalSettingInfo(c *gin.Context) { | ||||
| 	setting, err := settingService.GetTerminalInfo() | ||||
| 	if err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
| 	} | ||||
| 	helper.SuccessWithData(c, setting) | ||||
| } | ||||
| 
 | ||||
| // @Tags System Setting | ||||
| // @Summary Load system available status | ||||
| // @Description 获取系统可用状态 | ||||
|  | @ -61,6 +76,28 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) { | |||
| 	helper.SuccessWithData(c, nil) | ||||
| } | ||||
| 
 | ||||
| // @Tags System Setting | ||||
| // @Summary Update system terminal setting | ||||
| // @Description 更新系统终端配置 | ||||
| // @Accept json | ||||
| // @Param request body dto.TerminalInfo true "request" | ||||
| // @Success 200 | ||||
| // @Security ApiKeyAuth | ||||
| // @Router /core/settings/terminal/update [post] | ||||
| // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统终端配置","formatEN":"update system terminal setting"} | ||||
| func (b *BaseApi) UpdateTerminalSetting(c *gin.Context) { | ||||
| 	var req dto.TerminalInfo | ||||
| 	if err := helper.CheckBindAndValidate(&req, c); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if err := settingService.UpdateTerminal(req); err != nil { | ||||
| 		helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) | ||||
| 		return | ||||
| 	} | ||||
| 	helper.SuccessWithData(c, nil) | ||||
| } | ||||
| 
 | ||||
| // @Tags System Setting | ||||
| // @Summary Update proxy setting | ||||
| // @Description 服务器代理配置 | ||||
|  |  | |||
|  | @ -196,3 +196,13 @@ type XpackHideMenu struct { | |||
| 	Path     string          `json:"path,omitempty"` | ||||
| 	Children []XpackHideMenu `json:"children,omitempty"` | ||||
| } | ||||
| 
 | ||||
| type TerminalInfo struct { | ||||
| 	LineHeight        string `json:"lineHeight"` | ||||
| 	LetterSpacing     string `json:"letterSpacing"` | ||||
| 	FontSize          string `json:"fontSize"` | ||||
| 	CursorBlink       string `json:"cursorBlink"` | ||||
| 	CursorStyle       string `json:"cursorStyle"` | ||||
| 	Scrollback        string `json:"scrollback"` | ||||
| 	ScrollSensitivity string `json:"scrollSensitivity"` | ||||
| } | ||||
|  |  | |||
|  | @ -36,6 +36,9 @@ type ISettingService interface { | |||
| 	UpdateSSL(c *gin.Context, req dto.SSLUpdate) error | ||||
| 	LoadFromCert() (*dto.SSLInfo, error) | ||||
| 	HandlePasswordExpired(c *gin.Context, old, new string) error | ||||
| 
 | ||||
| 	GetTerminalInfo() (*dto.TerminalInfo, error) | ||||
| 	UpdateTerminal(req dto.TerminalInfo) error | ||||
| } | ||||
| 
 | ||||
| func NewISettingService() ISettingService { | ||||
|  | @ -338,6 +341,50 @@ func (u *SettingService) HandlePasswordExpired(c *gin.Context, old, new string) | |||
| 	return constant.ErrInitialPassword | ||||
| } | ||||
| 
 | ||||
| func (u *SettingService) GetTerminalInfo() (*dto.TerminalInfo, error) { | ||||
| 	setting, err := settingRepo.List() | ||||
| 	if err != nil { | ||||
| 		return nil, constant.ErrRecordNotFound | ||||
| 	} | ||||
| 	settingMap := make(map[string]string) | ||||
| 	for _, set := range setting { | ||||
| 		settingMap[set.Key] = set.Value | ||||
| 	} | ||||
| 	var info dto.TerminalInfo | ||||
| 	arr, err := json.Marshal(settingMap) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if err := json.Unmarshal(arr, &info); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &info, err | ||||
| } | ||||
| func (u *SettingService) UpdateTerminal(req dto.TerminalInfo) error { | ||||
| 	if err := settingRepo.Update("LineHeight", req.LineHeight); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := settingRepo.Update("LetterSpacing", req.LetterSpacing); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := settingRepo.Update("FontSize", req.FontSize); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := settingRepo.Update("CursorBlink", req.CursorBlink); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := settingRepo.Update("CursorStyle", req.CursorStyle); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := settingRepo.Update("Scrollback", req.Scrollback); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := settingRepo.Update("ScrollSensitivity", req.ScrollSensitivity); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (u *SettingService) UpdatePassword(c *gin.Context, old, new string) error { | ||||
| 	if err := u.HandlePasswordExpired(c, old, new); err != nil { | ||||
| 		return err | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ func Init() { | |||
| 		migrations.InitOneDrive, | ||||
| 		migrations.InitMasterAddr, | ||||
| 		migrations.InitHost, | ||||
| 		migrations.InitTerminalSetting, | ||||
| 	}) | ||||
| 	if err := m.Migrate(); err != nil { | ||||
| 		global.LOG.Error(err) | ||||
|  |  | |||
|  | @ -205,3 +205,31 @@ var InitMasterAddr = &gormigrate.Migration{ | |||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| var InitTerminalSetting = &gormigrate.Migration{ | ||||
| 	ID: "20240814-init-terminal-setting", | ||||
| 	Migrate: func(tx *gorm.DB) error { | ||||
| 		if err := tx.Create(&model.Setting{Key: "LineHeight", Value: "1.2"}).Error; err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := tx.Create(&model.Setting{Key: "LetterSpacing", Value: "0"}).Error; err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := tx.Create(&model.Setting{Key: "FontSize", Value: "12"}).Error; err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := tx.Create(&model.Setting{Key: "CursorBlink", Value: "enable"}).Error; err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := tx.Create(&model.Setting{Key: "CursorStyle", Value: "block"}).Error; err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := tx.Create(&model.Setting{Key: "Scrollback", Value: "1000"}).Error; err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := tx.Create(&model.Setting{Key: "ScrollSensitivity", Value: "6"}).Error; err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
|  |  | |||
|  | @ -12,9 +12,11 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) { | |||
| 	baseApi := v2.ApiGroupApp.BaseApi | ||||
| 	{ | ||||
| 		settingRouter.POST("/search", baseApi.GetSettingInfo) | ||||
| 		settingRouter.POST("/terminal/search", baseApi.GetTerminalSettingInfo) | ||||
| 		settingRouter.POST("/expired/handle", baseApi.HandlePasswordExpired) | ||||
| 		settingRouter.GET("/search/available", baseApi.GetSystemAvailable) | ||||
| 		settingRouter.POST("/update", baseApi.UpdateSetting) | ||||
| 		settingRouter.POST("/terminal/update", baseApi.UpdateTerminalSetting) | ||||
| 		settingRouter.GET("/interface", baseApi.LoadInterfaceAddr) | ||||
| 		settingRouter.POST("/menu/update", baseApi.UpdateMenu) | ||||
| 		settingRouter.POST("/proxy/update", baseApi.UpdateProxy) | ||||
|  |  | |||
|  | @ -59,6 +59,15 @@ export namespace Setting { | |||
|         proxyPasswd: string; | ||||
|         proxyPasswdKeep: string; | ||||
|     } | ||||
|     export interface TerminalInfo { | ||||
|         lineHeight: string; | ||||
|         letterSpacing: string; | ||||
|         fontSize: string; | ||||
|         cursorBlink: string; | ||||
|         cursorStyle: string; | ||||
|         scrollback: string; | ||||
|         scrollSensitivity: string; | ||||
|     } | ||||
|     export interface SettingUpdate { | ||||
|         key: string; | ||||
|         value: string; | ||||
|  |  | |||
|  | @ -33,6 +33,12 @@ export const loadDaemonJsonPath = () => { | |||
| export const getSettingInfo = () => { | ||||
|     return http.post<Setting.SettingInfo>(`/core/settings/search`); | ||||
| }; | ||||
| export const getTerminalInfo = () => { | ||||
|     return http.post<Setting.TerminalInfo>(`/core/settings/terminal/search`); | ||||
| }; | ||||
| export const UpdateTerminalInfo = (param: Setting.TerminalInfo) => { | ||||
|     return http.post(`/core/settings/terminal/update`, param); | ||||
| }; | ||||
| export const getSystemAvailable = () => { | ||||
|     return http.get(`/settings/search/available`); | ||||
| }; | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <template> | ||||
|     <div ref="terminalElement" @wheel="onTermWheel"></div> | ||||
|     <div ref="terminalElement"></div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
|  | @ -8,6 +8,7 @@ import { Terminal } from '@xterm/xterm'; | |||
| import '@xterm/xterm/css/xterm.css'; | ||||
| import { FitAddon } from '@xterm/addon-fit'; | ||||
| import { Base64 } from 'js-base64'; | ||||
| import { TerminalStore } from '@/store'; | ||||
| 
 | ||||
| const terminalElement = ref<HTMLDivElement | null>(null); | ||||
| const fitAddon = new FitAddon(); | ||||
|  | @ -29,6 +30,33 @@ const readyWatcher = watch( | |||
|     }, | ||||
| ); | ||||
| 
 | ||||
| const terminalStore = TerminalStore(); | ||||
| const lineHeight = computed(() => terminalStore.lineHeight); | ||||
| const fontSize = computed(() => terminalStore.fontSize); | ||||
| const letterSpacing = computed(() => terminalStore.letterSpacing); | ||||
| watch([lineHeight, fontSize, letterSpacing], ([newLineHeight, newFontSize, newLetterSpacing]) => { | ||||
|     term.value.options.lineHeight = newLineHeight; | ||||
|     term.value.options.letterSpacing = newLetterSpacing; | ||||
|     term.value.options.fontSize = newFontSize; | ||||
|     changeTerminalSize(); | ||||
| }); | ||||
| const cursorStyle = computed(() => terminalStore.cursorStyle); | ||||
| watch(cursorStyle, (newCursorStyle) => { | ||||
|     term.value.options.cursorStyle = newCursorStyle; | ||||
| }); | ||||
| const cursorBlink = computed(() => terminalStore.cursorBlink); | ||||
| watch(cursorBlink, (newCursorBlink) => { | ||||
|     term.value.options.cursorBlink = newCursorBlink === 'enable'; | ||||
| }); | ||||
| const scrollback = computed(() => terminalStore.scrollback); | ||||
| watch(scrollback, (newScrollback) => { | ||||
|     term.value.options.scrollback = newScrollback; | ||||
| }); | ||||
| const scrollSensitivity = computed(() => terminalStore.scrollSensitivity); | ||||
| watch(scrollSensitivity, (newScrollSensitivity) => { | ||||
|     term.value.options.scrollSensitivity = newScrollSensitivity; | ||||
| }); | ||||
| 
 | ||||
| interface WsProps { | ||||
|     endpoint: string; | ||||
|     args: string; | ||||
|  | @ -58,7 +86,6 @@ const newTerm = () => { | |||
|         cursorStyle: 'underline', | ||||
|         scrollback: 1000, | ||||
|         scrollSensitivity: 15, | ||||
|         tabStopWidth: 4, | ||||
|     }); | ||||
| }; | ||||
| 
 | ||||
|  | @ -117,25 +144,6 @@ function changeTerminalSize() { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Support for Ctrl+MouseWheel to scaling fonts | ||||
|  * @param event WheelEvent | ||||
|  */ | ||||
| const onTermWheel = (event: WheelEvent) => { | ||||
|     if (event.ctrlKey) { | ||||
|         event.preventDefault(); | ||||
|         if (event.deltaY > 0) { | ||||
|             // web font-size mini 12px | ||||
|             if (term.value.options.fontSize > 12) { | ||||
|                 term.value.options.fontSize = term.value.options.fontSize - 1; | ||||
|             } | ||||
|         } else { | ||||
|             term.value.options.fontSize = term.value.options.fontSize + 1; | ||||
|         } | ||||
|         changeTerminalSize(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| // terminal 相关代码 end | ||||
| 
 | ||||
| // websocket 相关代码 start | ||||
|  | @ -201,7 +209,7 @@ const errorRealTerminal = (ex: any) => { | |||
| 
 | ||||
| const closeRealTerminal = (ev: CloseEvent) => { | ||||
|     if (heartbeatTimer.value) { | ||||
|         clearInterval(heartbeatTimer.value); | ||||
|         clearInterval(Number(heartbeatTimer.value)); | ||||
|     } | ||||
|     term.value.write('The connection has been disconnected.'); | ||||
|     term.value.write(ev.reason); | ||||
|  |  | |||
|  | @ -24,6 +24,7 @@ const message = { | |||
|             confirm: 'Confirm', | ||||
|             cancel: 'Cancel', | ||||
|             reset: 'Reset', | ||||
|             setDefault: 'Restore Default', | ||||
|             restart: 'Restart', | ||||
|             conn: 'Connect', | ||||
|             disconn: 'Disconnect', | ||||
|  | @ -319,7 +320,7 @@ const message = { | |||
|         security: 'Security', | ||||
|         files: 'File', | ||||
|         monitor: 'Monitor', | ||||
|         terminal: 'Terminal', | ||||
|         terminal: 'Web Terminal', | ||||
|         settings: 'Setting', | ||||
|         toolbox: 'Toolbox', | ||||
|         logs: 'Log', | ||||
|  | @ -994,6 +995,17 @@ const message = { | |||
|         key: 'Private key', | ||||
|         keyPassword: 'Private key password', | ||||
|         emptyTerminal: 'No terminal is currently connected', | ||||
|         lineHeight: 'Line Height', | ||||
|         letterSpacing: 'Letter Spacing', | ||||
|         fontSize: 'Font Size', | ||||
|         cursorBlink: 'Cursor Blink', | ||||
|         cursorStyle: 'Cursor Style', | ||||
|         cursorUnderline: 'Underline', | ||||
|         cursorBlock: 'Block', | ||||
|         cursorBar: 'Bar', | ||||
|         scrollback: 'Scrollback', | ||||
|         scrollSensitivity: 'Scroll Sensitivity', | ||||
|         saveHelper: 'Are you sure you want to save the current terminal configuration?', | ||||
|     }, | ||||
|     toolbox: { | ||||
|         swap: { | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ const message = { | |||
|             confirm: '確認', | ||||
|             cancel: '取消', | ||||
|             reset: '重置', | ||||
|             setDefault: '恢復預設', | ||||
|             restart: '重啟', | ||||
|             conn: '連接', | ||||
|             disconn: '斷開', | ||||
|  | @ -313,7 +314,7 @@ const message = { | |||
|         host: '主機', | ||||
|         files: '文件', | ||||
|         monitor: '監控', | ||||
|         terminal: '終端', | ||||
|         terminal: 'Web終端', | ||||
|         settings: '面板設置', | ||||
|         toolbox: '工具箱', | ||||
|         logs: '日誌審計', | ||||
|  | @ -947,6 +948,17 @@ const message = { | |||
|         key: '私鑰', | ||||
|         keyPassword: '私鑰密碼', | ||||
|         emptyTerminal: '暫無終端連接', | ||||
|         lineHeight: '字體行高', | ||||
|         letterSpacing: '字體間距', | ||||
|         fontSize: '字體大小', | ||||
|         cursorBlink: '光標閃爍', | ||||
|         cursorStyle: '光標樣式', | ||||
|         cursorUnderline: '下劃線', | ||||
|         cursorBlock: '塊狀', | ||||
|         cursorBar: '條形', | ||||
|         scrollback: '滾動行數', | ||||
|         scrollSensitivity: '滾動速度', | ||||
|         saveHelper: '是否確認保存當前終端配置?', | ||||
|     }, | ||||
|     toolbox: { | ||||
|         swap: { | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ const message = { | |||
|             confirm: '确认', | ||||
|             cancel: '取消', | ||||
|             reset: '重置', | ||||
|             setDefault: '恢复默认', | ||||
|             restart: '重启', | ||||
|             conn: '连接', | ||||
|             disconn: '断开', | ||||
|  | @ -948,6 +949,17 @@ const message = { | |||
|         key: '私钥', | ||||
|         keyPassword: '私钥密码', | ||||
|         emptyTerminal: '暂无终端连接', | ||||
|         lineHeight: '字体行高', | ||||
|         letterSpacing: '字体间距', | ||||
|         fontSize: '字体大小', | ||||
|         cursorBlink: '光标闪烁', | ||||
|         cursorStyle: '光标样式', | ||||
|         cursorUnderline: '下划线', | ||||
|         cursorBlock: '块状', | ||||
|         cursorBar: '条形', | ||||
|         scrollback: '滚动行数', | ||||
|         scrollSensitivity: '滚动速度', | ||||
|         saveHelper: '是否确认保存当前终端配置?', | ||||
|     }, | ||||
|     toolbox: { | ||||
|         swap: { | ||||
|  | @ -1121,7 +1133,7 @@ const message = { | |||
|         role: '权限', | ||||
|         info: '属性', | ||||
|         linkFile: '软连接文件', | ||||
|         terminal: '终端', | ||||
|         terminal: 'Web终端', | ||||
|         shareList: '分享列表', | ||||
|         zip: '压缩', | ||||
|         group: '用户组', | ||||
|  |  | |||
|  | @ -3,10 +3,11 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; | |||
| import GlobalStore from './modules/global'; | ||||
| import MenuStore from './modules/menu'; | ||||
| import TabsStore from './modules/tabs'; | ||||
| import TerminalStore from './modules/terminal'; | ||||
| 
 | ||||
| const pinia = createPinia(); | ||||
| pinia.use(piniaPluginPersistedstate); | ||||
| 
 | ||||
| export { GlobalStore, MenuStore, TabsStore }; | ||||
| export { GlobalStore, MenuStore, TabsStore, TerminalStore }; | ||||
| 
 | ||||
| export default pinia; | ||||
|  |  | |||
|  | @ -46,3 +46,13 @@ export interface MenuState { | |||
|     menuList: RouteRecordRaw[]; | ||||
|     withoutAnimation: boolean; | ||||
| } | ||||
| 
 | ||||
| export interface TerminalState { | ||||
|     lineHeight: number; | ||||
|     letterSpacing: number; | ||||
|     fontSize: number; | ||||
|     cursorBlink: string; | ||||
|     cursorStyle: string; | ||||
|     scrollback: number; | ||||
|     scrollSensitivity: number; | ||||
| } | ||||
|  |  | |||
							
								
								
									
										42
									
								
								frontend/src/store/modules/terminal.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								frontend/src/store/modules/terminal.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| import { defineStore } from 'pinia'; | ||||
| import piniaPersistConfig from '@/config/pinia-persist'; | ||||
| import { TerminalState } from '../interface'; | ||||
| 
 | ||||
| export const TerminalStore = defineStore({ | ||||
|     id: 'TerminalState', | ||||
|     state: (): TerminalState => ({ | ||||
|         lineHeight: 1.2, | ||||
|         letterSpacing: 1.2, | ||||
|         fontSize: 12, | ||||
|         cursorBlink: 'enable', | ||||
|         cursorStyle: 'underline', | ||||
|         scrollback: 1000, | ||||
|         scrollSensitivity: 10, | ||||
|     }), | ||||
|     actions: { | ||||
|         setLineHeight(lineHeight: number) { | ||||
|             this.lineHeight = lineHeight; | ||||
|         }, | ||||
|         setLetterSpacing(letterSpacing: number) { | ||||
|             this.letterSpacing = letterSpacing; | ||||
|         }, | ||||
|         setFontSize(fontSize: number) { | ||||
|             this.fontSize = fontSize; | ||||
|         }, | ||||
|         setCursorBlink(cursorBlink: string) { | ||||
|             this.cursorBlink = cursorBlink; | ||||
|         }, | ||||
|         setCursorStyle(cursorStyle: string) { | ||||
|             this.cursorStyle = cursorStyle; | ||||
|         }, | ||||
|         setScrollback(scrollback: number) { | ||||
|             this.scrollback = scrollback; | ||||
|         }, | ||||
|         setScrollSensitivity(scrollSensitivity: number) { | ||||
|             this.scrollSensitivity = scrollSensitivity; | ||||
|         }, | ||||
|     }, | ||||
|     persist: piniaPersistConfig('TerminalState'), | ||||
| }); | ||||
| 
 | ||||
| export default TerminalStore; | ||||
|  | @ -96,7 +96,7 @@ | |||
| <script setup lang="ts"> | ||||
| import { dateFormat } from '@/utils/util'; | ||||
| import { onMounted, ref } from 'vue'; | ||||
| import { searchBackup, deleteBackup, refreshOneDrive } from '@/api/modules/setting'; | ||||
| import { searchBackup, deleteBackup, refreshOneDrive } from '@/api/modules/backup'; | ||||
| import Operate from '@/views/setting/backup-account/operate/index.vue'; | ||||
| import { Backup } from '@/api/interface/backup'; | ||||
| import i18n from '@/lang'; | ||||
|  |  | |||
|  | @ -291,7 +291,7 @@ import { Rules } from '@/global/form-rules'; | |||
| import i18n from '@/lang'; | ||||
| import { ElForm } from 'element-plus'; | ||||
| import { Backup } from '@/api/interface/backup'; | ||||
| import { addBackup, editBackup, getOneDriveInfo, listBucket } from '@/api/modules/setting'; | ||||
| import { addBackup, editBackup, getOneDriveInfo, listBucket } from '@/api/modules/backup'; | ||||
| import { cities } from './../helper'; | ||||
| import { deepCopy, spliceHttp, splitHttp } from '@/utils/util'; | ||||
| import { MsgSuccess } from '@/utils/message'; | ||||
|  |  | |||
|  | @ -11,6 +11,9 @@ | |||
|                 <el-radio-button class="router_card_button" size="large" value="command"> | ||||
|                     {{ $t('terminal.quickCommand') }} | ||||
|                 </el-radio-button> | ||||
|                 <el-radio-button class="router_card_button" size="large" value="setting"> | ||||
|                     {{ $t('container.setting') }} | ||||
|                 </el-radio-button> | ||||
|             </el-radio-group> | ||||
|         </el-card> | ||||
| 
 | ||||
|  | @ -23,6 +26,9 @@ | |||
|         <div v-if="activeNames === 'command'"> | ||||
|             <CommandTab ref="commandTabRef" /> | ||||
|         </div> | ||||
|         <div v-if="activeNames === 'setting'"> | ||||
|             <SettingTab ref="settingTabRef" /> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -30,12 +36,17 @@ | |||
| import HostTab from '@/views/terminal/host/index.vue'; | ||||
| import CommandTab from '@/views/terminal/command/index.vue'; | ||||
| import TerminalTab from '@/views/terminal/terminal/index.vue'; | ||||
| import SettingTab from '@/views/terminal/setting/index.vue'; | ||||
| import { onMounted, onUnmounted, ref } from 'vue'; | ||||
| import { getTerminalInfo } from '@/api/modules/setting'; | ||||
| import { TerminalStore } from '@/store'; | ||||
| 
 | ||||
| const terminalStore = TerminalStore(); | ||||
| const activeNames = ref<string>('terminal'); | ||||
| const hostTabRef = ref(); | ||||
| const commandTabRef = ref(); | ||||
| const terminalTabRef = ref(); | ||||
| const settingTabRef = ref(); | ||||
| 
 | ||||
| const handleChange = (tab: any) => { | ||||
|     if (tab === 'host') { | ||||
|  | @ -47,9 +58,25 @@ const handleChange = (tab: any) => { | |||
|     if (tab === 'terminal') { | ||||
|         terminalTabRef.value!.acceptParams(); | ||||
|     } | ||||
|     if (tab === 'setting') { | ||||
|         settingTabRef.value!.acceptParams(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| const loadTerminalSetting = async () => { | ||||
|     await getTerminalInfo().then((res) => { | ||||
|         terminalStore.setLineHeight(Number(res.data.lineHeight)); | ||||
|         terminalStore.setLetterSpacing(Number(res.data.letterSpacing)); | ||||
|         terminalStore.setFontSize(Number(res.data.fontSize)); | ||||
|         terminalStore.setCursorBlink(res.data.cursorBlink); | ||||
|         terminalStore.setCursorStyle(res.data.cursorStyle); | ||||
|         terminalStore.setScrollback(Number(res.data.scrollback)); | ||||
|         terminalStore.setScrollSensitivity(Number(res.data.scrollSensitivity)); | ||||
|     }); | ||||
| }; | ||||
| 
 | ||||
| onMounted(() => { | ||||
|     loadTerminalSetting(); | ||||
|     handleChange('terminal'); | ||||
| }); | ||||
| onUnmounted(() => { | ||||
|  |  | |||
							
								
								
									
										241
									
								
								frontend/src/views/terminal/setting/index.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								frontend/src/views/terminal/setting/index.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,241 @@ | |||
| <template> | ||||
|     <div v-loading="loading"> | ||||
|         <LayoutContent :title="$t('container.setting')" :divider="true"> | ||||
|             <template #main> | ||||
|                 <el-form :model="form" label-position="left" label-width="150px"> | ||||
|                     <el-row> | ||||
|                         <el-col :span="1"><br /></el-col> | ||||
|                         <el-col :xs="24" :sm="20" :md="15" :lg="12" :xl="12"> | ||||
|                             <el-form-item :label="$t('terminal.lineHeight')"> | ||||
|                                 <el-input-number | ||||
|                                     class="formInput" | ||||
|                                     :min="1" | ||||
|                                     :max="2.0" | ||||
|                                     :precision="1" | ||||
|                                     :step="0.1" | ||||
|                                     v-model="form.lineHeight" | ||||
|                                     @change="changeItem()" | ||||
|                                 /> | ||||
|                             </el-form-item> | ||||
|                             <el-form-item :label="$t('terminal.letterSpacing')"> | ||||
|                                 <el-input-number | ||||
|                                     class="formInput" | ||||
|                                     :min="0" | ||||
|                                     :max="3.5" | ||||
|                                     :precision="1" | ||||
|                                     :step="0.5" | ||||
|                                     v-model="form.letterSpacing" | ||||
|                                     @change="changeItem()" | ||||
|                                 /> | ||||
|                             </el-form-item> | ||||
|                             <el-form-item :label="$t('terminal.fontSize')"> | ||||
|                                 <el-input-number | ||||
|                                     class="formInput" | ||||
|                                     :step="1" | ||||
|                                     :min="12" | ||||
|                                     :max="20" | ||||
|                                     v-model="form.fontSize" | ||||
|                                     @change="changeItem()" | ||||
|                                 /> | ||||
|                             </el-form-item> | ||||
| 
 | ||||
|                             <el-form-item> | ||||
|                                 <div class="terminal" ref="terminalElement"></div> | ||||
|                             </el-form-item> | ||||
| 
 | ||||
|                             <el-form-item :label="$t('terminal.cursorBlink')"> | ||||
|                                 <el-switch | ||||
|                                     v-model="form.cursorBlink" | ||||
|                                     active-value="enable" | ||||
|                                     inactive-value="disable" | ||||
|                                     @change="changeItem()" | ||||
|                                 /> | ||||
|                             </el-form-item> | ||||
|                             <el-form-item :label="$t('terminal.cursorStyle')"> | ||||
|                                 <el-select class="formInput" v-model="form.cursorStyle" @change="changeItem()"> | ||||
|                                     <el-option value="block" :label="$t('terminal.cursorBlock')" /> | ||||
|                                     <el-option value="underline" :label="$t('terminal.cursorUnderline')" /> | ||||
|                                     <el-option value="bar" :label="$t('terminal.cursorBar')" /> | ||||
|                                 </el-select> | ||||
|                             </el-form-item> | ||||
|                             <el-form-item :label="$t('terminal.scrollback')"> | ||||
|                                 <el-input-number | ||||
|                                     class="formInput" | ||||
|                                     :step="50" | ||||
|                                     :min="0" | ||||
|                                     :max="10000" | ||||
|                                     v-model="form.scrollback" | ||||
|                                     @change="changeItem()" | ||||
|                                 /> | ||||
|                             </el-form-item> | ||||
|                             <el-form-item :label="$t('terminal.scrollSensitivity')"> | ||||
|                                 <el-input-number | ||||
|                                     class="formInput" | ||||
|                                     :step="1" | ||||
|                                     :min="0" | ||||
|                                     :max="16" | ||||
|                                     v-model="form.scrollSensitivity" | ||||
|                                     @change="changeItem()" | ||||
|                                 /> | ||||
|                             </el-form-item> | ||||
|                             <el-form-item> | ||||
|                                 <el-button @click="onSetDefault()" plain> | ||||
|                                     {{ $t('commons.button.setDefault') }} | ||||
|                                 </el-button> | ||||
|                                 <el-button @click="search(true)" plain>{{ $t('commons.button.reset') }}</el-button> | ||||
|                                 <el-button @click="onSave" type="primary">{{ $t('commons.button.save') }}</el-button> | ||||
|                             </el-form-item> | ||||
|                         </el-col> | ||||
|                     </el-row> | ||||
|                 </el-form> | ||||
|             </template> | ||||
|         </LayoutContent> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import { ref, reactive } from 'vue'; | ||||
| import { ElForm } from 'element-plus'; | ||||
| import { getTerminalInfo, UpdateTerminalInfo } from '@/api/modules/setting'; | ||||
| import { Terminal } from '@xterm/xterm'; | ||||
| import '@xterm/xterm/css/xterm.css'; | ||||
| import { FitAddon } from '@xterm/addon-fit'; | ||||
| import i18n from '@/lang'; | ||||
| import { MsgSuccess } from '@/utils/message'; | ||||
| import { TerminalStore } from '@/store'; | ||||
| 
 | ||||
| const loading = ref(false); | ||||
| const terminalStore = TerminalStore(); | ||||
| 
 | ||||
| const terminalElement = ref<HTMLDivElement | null>(null); | ||||
| const fitAddon = new FitAddon(); | ||||
| const term = ref(); | ||||
| 
 | ||||
| const form = reactive({ | ||||
|     lineHeight: 1.2, | ||||
|     letterSpacing: 1.2, | ||||
|     fontSize: 12, | ||||
|     cursorBlink: 'enable', | ||||
|     cursorStyle: 'underline', | ||||
|     scrollback: 1000, | ||||
|     scrollSensitivity: 10, | ||||
| }); | ||||
| 
 | ||||
| const acceptParams = () => { | ||||
|     search(); | ||||
|     iniTerm(); | ||||
| }; | ||||
| 
 | ||||
| const search = async (withReset?: boolean) => { | ||||
|     loading.value = true; | ||||
|     await getTerminalInfo() | ||||
|         .then((res) => { | ||||
|             loading.value = false; | ||||
|             form.lineHeight = Number(res.data.lineHeight); | ||||
|             form.letterSpacing = Number(res.data.letterSpacing); | ||||
|             form.fontSize = Number(res.data.fontSize); | ||||
|             form.cursorBlink = res.data.cursorBlink; | ||||
|             form.cursorStyle = res.data.cursorStyle; | ||||
|             form.scrollback = Number(res.data.scrollback); | ||||
|             form.scrollSensitivity = Number(res.data.scrollSensitivity); | ||||
| 
 | ||||
|             if (withReset) { | ||||
|                 changeItem(); | ||||
|             } | ||||
|         }) | ||||
|         .catch(() => { | ||||
|             loading.value = false; | ||||
|         }); | ||||
| }; | ||||
| 
 | ||||
| const iniTerm = () => { | ||||
|     term.value = new Terminal({ | ||||
|         lineHeight: 1.2, | ||||
|         fontSize: 12, | ||||
|         fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace", | ||||
|         theme: { | ||||
|             background: '#000000', | ||||
|         }, | ||||
|         cursorBlink: true, | ||||
|         cursorStyle: 'block', | ||||
|         scrollback: 1000, | ||||
|         scrollSensitivity: 15, | ||||
|     }); | ||||
|     term.value.open(terminalElement.value); | ||||
|     term.value.loadAddon(fitAddon); | ||||
|     term.value.write('the first line \r\nthe second line'); | ||||
|     fitAddon.fit(); | ||||
| }; | ||||
| 
 | ||||
| const changeItem = () => { | ||||
|     term.value.options.lineHeight = form.lineHeight; | ||||
|     term.value.options.letterSpacing = form.letterSpacing; | ||||
|     term.value.options.fontSize = form.fontSize; | ||||
|     term.value.options.cursorBlink = form.cursorBlink === 'enable'; | ||||
|     term.value.options.cursorStyle = form.cursorStyle; | ||||
|     term.value.options.scrollback = form.scrollback; | ||||
|     term.value.options.scrollSensitivity = form.scrollSensitivity; | ||||
| 
 | ||||
|     fitAddon.fit(); | ||||
| }; | ||||
| 
 | ||||
| const onSetDefault = () => { | ||||
|     form.lineHeight = 1.2; | ||||
|     form.letterSpacing = 0; | ||||
|     form.fontSize = 12; | ||||
|     form.cursorBlink = 'enable'; | ||||
|     form.cursorStyle = 'block'; | ||||
|     form.scrollback = 1000; | ||||
|     form.scrollSensitivity = 6; | ||||
| 
 | ||||
|     changeItem(); | ||||
| }; | ||||
| 
 | ||||
| const onSave = () => { | ||||
|     ElMessageBox.confirm(i18n.global.t('terminal.saveHelper'), i18n.global.t('container.setting'), { | ||||
|         confirmButtonText: i18n.global.t('commons.button.confirm'), | ||||
|         cancelButtonText: i18n.global.t('commons.button.cancel'), | ||||
|         type: 'info', | ||||
|     }).then(async () => { | ||||
|         loading.value = true; | ||||
|         let param = { | ||||
|             lineHeight: form.lineHeight + '', | ||||
|             letterSpacing: form.letterSpacing + '', | ||||
|             fontSize: form.fontSize + '', | ||||
|             cursorBlink: form.cursorBlink, | ||||
|             cursorStyle: form.cursorStyle, | ||||
|             scrollback: form.scrollback + '', | ||||
|             scrollSensitivity: form.scrollSensitivity + '', | ||||
|         }; | ||||
|         await UpdateTerminalInfo(param) | ||||
|             .then(() => { | ||||
|                 MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); | ||||
|                 loading.value = false; | ||||
|                 terminalStore.setLineHeight(form.lineHeight); | ||||
|                 terminalStore.setLetterSpacing(form.letterSpacing); | ||||
|                 terminalStore.setFontSize(form.fontSize); | ||||
|                 terminalStore.setCursorBlink(form.cursorBlink); | ||||
|                 terminalStore.setCursorStyle(form.cursorStyle); | ||||
|                 terminalStore.setScrollback(form.scrollback); | ||||
|                 terminalStore.setScrollSensitivity(form.scrollSensitivity); | ||||
|             }) | ||||
|             .catch(() => { | ||||
|                 loading.value = false; | ||||
|             }); | ||||
|     }); | ||||
| }; | ||||
| 
 | ||||
| defineExpose({ | ||||
|     acceptParams, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="css" scoped> | ||||
| .formInput { | ||||
|     width: 100%; | ||||
| } | ||||
| .terminal { | ||||
|     width: 100%; | ||||
|     height: 100px; | ||||
| } | ||||
| </style> | ||||
		Loading…
	
	Add table
		
		Reference in a new issue