mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 08:19:15 +08:00
feat: 增加终端自定义配置 (#6208)
This commit is contained in:
parent
12b7f806f9
commit
cfe4fa7a92
@ -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…
x
Reference in New Issue
Block a user