mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-03-17 03:04:46 +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)
|
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
|
// @Tags System Setting
|
||||||
// @Summary Load system available status
|
// @Summary Load system available status
|
||||||
// @Description 获取系统可用状态
|
// @Description 获取系统可用状态
|
||||||
@ -61,6 +76,28 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, nil)
|
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
|
// @Tags System Setting
|
||||||
// @Summary Update proxy setting
|
// @Summary Update proxy setting
|
||||||
// @Description 服务器代理配置
|
// @Description 服务器代理配置
|
||||||
|
@ -196,3 +196,13 @@ type XpackHideMenu struct {
|
|||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
Children []XpackHideMenu `json:"children,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
|
UpdateSSL(c *gin.Context, req dto.SSLUpdate) error
|
||||||
LoadFromCert() (*dto.SSLInfo, error)
|
LoadFromCert() (*dto.SSLInfo, error)
|
||||||
HandlePasswordExpired(c *gin.Context, old, new string) error
|
HandlePasswordExpired(c *gin.Context, old, new string) error
|
||||||
|
|
||||||
|
GetTerminalInfo() (*dto.TerminalInfo, error)
|
||||||
|
UpdateTerminal(req dto.TerminalInfo) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewISettingService() ISettingService {
|
func NewISettingService() ISettingService {
|
||||||
@ -338,6 +341,50 @@ func (u *SettingService) HandlePasswordExpired(c *gin.Context, old, new string)
|
|||||||
return constant.ErrInitialPassword
|
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 {
|
func (u *SettingService) UpdatePassword(c *gin.Context, old, new string) error {
|
||||||
if err := u.HandlePasswordExpired(c, old, new); err != nil {
|
if err := u.HandlePasswordExpired(c, old, new); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -14,6 +14,7 @@ func Init() {
|
|||||||
migrations.InitOneDrive,
|
migrations.InitOneDrive,
|
||||||
migrations.InitMasterAddr,
|
migrations.InitMasterAddr,
|
||||||
migrations.InitHost,
|
migrations.InitHost,
|
||||||
|
migrations.InitTerminalSetting,
|
||||||
})
|
})
|
||||||
if err := m.Migrate(); err != nil {
|
if err := m.Migrate(); err != nil {
|
||||||
global.LOG.Error(err)
|
global.LOG.Error(err)
|
||||||
|
@ -205,3 +205,31 @@ var InitMasterAddr = &gormigrate.Migration{
|
|||||||
return nil
|
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
|
baseApi := v2.ApiGroupApp.BaseApi
|
||||||
{
|
{
|
||||||
settingRouter.POST("/search", baseApi.GetSettingInfo)
|
settingRouter.POST("/search", baseApi.GetSettingInfo)
|
||||||
|
settingRouter.POST("/terminal/search", baseApi.GetTerminalSettingInfo)
|
||||||
settingRouter.POST("/expired/handle", baseApi.HandlePasswordExpired)
|
settingRouter.POST("/expired/handle", baseApi.HandlePasswordExpired)
|
||||||
settingRouter.GET("/search/available", baseApi.GetSystemAvailable)
|
settingRouter.GET("/search/available", baseApi.GetSystemAvailable)
|
||||||
settingRouter.POST("/update", baseApi.UpdateSetting)
|
settingRouter.POST("/update", baseApi.UpdateSetting)
|
||||||
|
settingRouter.POST("/terminal/update", baseApi.UpdateTerminalSetting)
|
||||||
settingRouter.GET("/interface", baseApi.LoadInterfaceAddr)
|
settingRouter.GET("/interface", baseApi.LoadInterfaceAddr)
|
||||||
settingRouter.POST("/menu/update", baseApi.UpdateMenu)
|
settingRouter.POST("/menu/update", baseApi.UpdateMenu)
|
||||||
settingRouter.POST("/proxy/update", baseApi.UpdateProxy)
|
settingRouter.POST("/proxy/update", baseApi.UpdateProxy)
|
||||||
|
@ -59,6 +59,15 @@ export namespace Setting {
|
|||||||
proxyPasswd: string;
|
proxyPasswd: string;
|
||||||
proxyPasswdKeep: string;
|
proxyPasswdKeep: string;
|
||||||
}
|
}
|
||||||
|
export interface TerminalInfo {
|
||||||
|
lineHeight: string;
|
||||||
|
letterSpacing: string;
|
||||||
|
fontSize: string;
|
||||||
|
cursorBlink: string;
|
||||||
|
cursorStyle: string;
|
||||||
|
scrollback: string;
|
||||||
|
scrollSensitivity: string;
|
||||||
|
}
|
||||||
export interface SettingUpdate {
|
export interface SettingUpdate {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
@ -33,6 +33,12 @@ export const loadDaemonJsonPath = () => {
|
|||||||
export const getSettingInfo = () => {
|
export const getSettingInfo = () => {
|
||||||
return http.post<Setting.SettingInfo>(`/core/settings/search`);
|
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 = () => {
|
export const getSystemAvailable = () => {
|
||||||
return http.get(`/settings/search/available`);
|
return http.get(`/settings/search/available`);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="terminalElement" @wheel="onTermWheel"></div>
|
<div ref="terminalElement"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -8,6 +8,7 @@ import { Terminal } from '@xterm/xterm';
|
|||||||
import '@xterm/xterm/css/xterm.css';
|
import '@xterm/xterm/css/xterm.css';
|
||||||
import { FitAddon } from '@xterm/addon-fit';
|
import { FitAddon } from '@xterm/addon-fit';
|
||||||
import { Base64 } from 'js-base64';
|
import { Base64 } from 'js-base64';
|
||||||
|
import { TerminalStore } from '@/store';
|
||||||
|
|
||||||
const terminalElement = ref<HTMLDivElement | null>(null);
|
const terminalElement = ref<HTMLDivElement | null>(null);
|
||||||
const fitAddon = new FitAddon();
|
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 {
|
interface WsProps {
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
args: string;
|
args: string;
|
||||||
@ -58,7 +86,6 @@ const newTerm = () => {
|
|||||||
cursorStyle: 'underline',
|
cursorStyle: 'underline',
|
||||||
scrollback: 1000,
|
scrollback: 1000,
|
||||||
scrollSensitivity: 15,
|
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
|
// terminal 相关代码 end
|
||||||
|
|
||||||
// websocket 相关代码 start
|
// websocket 相关代码 start
|
||||||
@ -201,7 +209,7 @@ const errorRealTerminal = (ex: any) => {
|
|||||||
|
|
||||||
const closeRealTerminal = (ev: CloseEvent) => {
|
const closeRealTerminal = (ev: CloseEvent) => {
|
||||||
if (heartbeatTimer.value) {
|
if (heartbeatTimer.value) {
|
||||||
clearInterval(heartbeatTimer.value);
|
clearInterval(Number(heartbeatTimer.value));
|
||||||
}
|
}
|
||||||
term.value.write('The connection has been disconnected.');
|
term.value.write('The connection has been disconnected.');
|
||||||
term.value.write(ev.reason);
|
term.value.write(ev.reason);
|
||||||
|
@ -24,6 +24,7 @@ const message = {
|
|||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
|
setDefault: 'Restore Default',
|
||||||
restart: 'Restart',
|
restart: 'Restart',
|
||||||
conn: 'Connect',
|
conn: 'Connect',
|
||||||
disconn: 'Disconnect',
|
disconn: 'Disconnect',
|
||||||
@ -319,7 +320,7 @@ const message = {
|
|||||||
security: 'Security',
|
security: 'Security',
|
||||||
files: 'File',
|
files: 'File',
|
||||||
monitor: 'Monitor',
|
monitor: 'Monitor',
|
||||||
terminal: 'Terminal',
|
terminal: 'Web Terminal',
|
||||||
settings: 'Setting',
|
settings: 'Setting',
|
||||||
toolbox: 'Toolbox',
|
toolbox: 'Toolbox',
|
||||||
logs: 'Log',
|
logs: 'Log',
|
||||||
@ -994,6 +995,17 @@ const message = {
|
|||||||
key: 'Private key',
|
key: 'Private key',
|
||||||
keyPassword: 'Private key password',
|
keyPassword: 'Private key password',
|
||||||
emptyTerminal: 'No terminal is currently connected',
|
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: {
|
toolbox: {
|
||||||
swap: {
|
swap: {
|
||||||
|
@ -23,6 +23,7 @@ const message = {
|
|||||||
confirm: '確認',
|
confirm: '確認',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
reset: '重置',
|
reset: '重置',
|
||||||
|
setDefault: '恢復預設',
|
||||||
restart: '重啟',
|
restart: '重啟',
|
||||||
conn: '連接',
|
conn: '連接',
|
||||||
disconn: '斷開',
|
disconn: '斷開',
|
||||||
@ -313,7 +314,7 @@ const message = {
|
|||||||
host: '主機',
|
host: '主機',
|
||||||
files: '文件',
|
files: '文件',
|
||||||
monitor: '監控',
|
monitor: '監控',
|
||||||
terminal: '終端',
|
terminal: 'Web終端',
|
||||||
settings: '面板設置',
|
settings: '面板設置',
|
||||||
toolbox: '工具箱',
|
toolbox: '工具箱',
|
||||||
logs: '日誌審計',
|
logs: '日誌審計',
|
||||||
@ -947,6 +948,17 @@ const message = {
|
|||||||
key: '私鑰',
|
key: '私鑰',
|
||||||
keyPassword: '私鑰密碼',
|
keyPassword: '私鑰密碼',
|
||||||
emptyTerminal: '暫無終端連接',
|
emptyTerminal: '暫無終端連接',
|
||||||
|
lineHeight: '字體行高',
|
||||||
|
letterSpacing: '字體間距',
|
||||||
|
fontSize: '字體大小',
|
||||||
|
cursorBlink: '光標閃爍',
|
||||||
|
cursorStyle: '光標樣式',
|
||||||
|
cursorUnderline: '下劃線',
|
||||||
|
cursorBlock: '塊狀',
|
||||||
|
cursorBar: '條形',
|
||||||
|
scrollback: '滾動行數',
|
||||||
|
scrollSensitivity: '滾動速度',
|
||||||
|
saveHelper: '是否確認保存當前終端配置?',
|
||||||
},
|
},
|
||||||
toolbox: {
|
toolbox: {
|
||||||
swap: {
|
swap: {
|
||||||
|
@ -23,6 +23,7 @@ const message = {
|
|||||||
confirm: '确认',
|
confirm: '确认',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
reset: '重置',
|
reset: '重置',
|
||||||
|
setDefault: '恢复默认',
|
||||||
restart: '重启',
|
restart: '重启',
|
||||||
conn: '连接',
|
conn: '连接',
|
||||||
disconn: '断开',
|
disconn: '断开',
|
||||||
@ -948,6 +949,17 @@ const message = {
|
|||||||
key: '私钥',
|
key: '私钥',
|
||||||
keyPassword: '私钥密码',
|
keyPassword: '私钥密码',
|
||||||
emptyTerminal: '暂无终端连接',
|
emptyTerminal: '暂无终端连接',
|
||||||
|
lineHeight: '字体行高',
|
||||||
|
letterSpacing: '字体间距',
|
||||||
|
fontSize: '字体大小',
|
||||||
|
cursorBlink: '光标闪烁',
|
||||||
|
cursorStyle: '光标样式',
|
||||||
|
cursorUnderline: '下划线',
|
||||||
|
cursorBlock: '块状',
|
||||||
|
cursorBar: '条形',
|
||||||
|
scrollback: '滚动行数',
|
||||||
|
scrollSensitivity: '滚动速度',
|
||||||
|
saveHelper: '是否确认保存当前终端配置?',
|
||||||
},
|
},
|
||||||
toolbox: {
|
toolbox: {
|
||||||
swap: {
|
swap: {
|
||||||
@ -1121,7 +1133,7 @@ const message = {
|
|||||||
role: '权限',
|
role: '权限',
|
||||||
info: '属性',
|
info: '属性',
|
||||||
linkFile: '软连接文件',
|
linkFile: '软连接文件',
|
||||||
terminal: '终端',
|
terminal: 'Web终端',
|
||||||
shareList: '分享列表',
|
shareList: '分享列表',
|
||||||
zip: '压缩',
|
zip: '压缩',
|
||||||
group: '用户组',
|
group: '用户组',
|
||||||
|
@ -3,10 +3,11 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
|
|||||||
import GlobalStore from './modules/global';
|
import GlobalStore from './modules/global';
|
||||||
import MenuStore from './modules/menu';
|
import MenuStore from './modules/menu';
|
||||||
import TabsStore from './modules/tabs';
|
import TabsStore from './modules/tabs';
|
||||||
|
import TerminalStore from './modules/terminal';
|
||||||
|
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
pinia.use(piniaPluginPersistedstate);
|
pinia.use(piniaPluginPersistedstate);
|
||||||
|
|
||||||
export { GlobalStore, MenuStore, TabsStore };
|
export { GlobalStore, MenuStore, TabsStore, TerminalStore };
|
||||||
|
|
||||||
export default pinia;
|
export default pinia;
|
||||||
|
@ -46,3 +46,13 @@ export interface MenuState {
|
|||||||
menuList: RouteRecordRaw[];
|
menuList: RouteRecordRaw[];
|
||||||
withoutAnimation: boolean;
|
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">
|
<script setup lang="ts">
|
||||||
import { dateFormat } from '@/utils/util';
|
import { dateFormat } from '@/utils/util';
|
||||||
import { onMounted, ref } from 'vue';
|
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 Operate from '@/views/setting/backup-account/operate/index.vue';
|
||||||
import { Backup } from '@/api/interface/backup';
|
import { Backup } from '@/api/interface/backup';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
|
@ -291,7 +291,7 @@ import { Rules } from '@/global/form-rules';
|
|||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { ElForm } from 'element-plus';
|
import { ElForm } from 'element-plus';
|
||||||
import { Backup } from '@/api/interface/backup';
|
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 { cities } from './../helper';
|
||||||
import { deepCopy, spliceHttp, splitHttp } from '@/utils/util';
|
import { deepCopy, spliceHttp, splitHttp } from '@/utils/util';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
@ -11,6 +11,9 @@
|
|||||||
<el-radio-button class="router_card_button" size="large" value="command">
|
<el-radio-button class="router_card_button" size="large" value="command">
|
||||||
{{ $t('terminal.quickCommand') }}
|
{{ $t('terminal.quickCommand') }}
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
|
<el-radio-button class="router_card_button" size="large" value="setting">
|
||||||
|
{{ $t('container.setting') }}
|
||||||
|
</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
@ -23,6 +26,9 @@
|
|||||||
<div v-if="activeNames === 'command'">
|
<div v-if="activeNames === 'command'">
|
||||||
<CommandTab ref="commandTabRef" />
|
<CommandTab ref="commandTabRef" />
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="activeNames === 'setting'">
|
||||||
|
<SettingTab ref="settingTabRef" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -30,12 +36,17 @@
|
|||||||
import HostTab from '@/views/terminal/host/index.vue';
|
import HostTab from '@/views/terminal/host/index.vue';
|
||||||
import CommandTab from '@/views/terminal/command/index.vue';
|
import CommandTab from '@/views/terminal/command/index.vue';
|
||||||
import TerminalTab from '@/views/terminal/terminal/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 { onMounted, onUnmounted, ref } from 'vue';
|
||||||
|
import { getTerminalInfo } from '@/api/modules/setting';
|
||||||
|
import { TerminalStore } from '@/store';
|
||||||
|
|
||||||
|
const terminalStore = TerminalStore();
|
||||||
const activeNames = ref<string>('terminal');
|
const activeNames = ref<string>('terminal');
|
||||||
const hostTabRef = ref();
|
const hostTabRef = ref();
|
||||||
const commandTabRef = ref();
|
const commandTabRef = ref();
|
||||||
const terminalTabRef = ref();
|
const terminalTabRef = ref();
|
||||||
|
const settingTabRef = ref();
|
||||||
|
|
||||||
const handleChange = (tab: any) => {
|
const handleChange = (tab: any) => {
|
||||||
if (tab === 'host') {
|
if (tab === 'host') {
|
||||||
@ -47,9 +58,25 @@ const handleChange = (tab: any) => {
|
|||||||
if (tab === 'terminal') {
|
if (tab === 'terminal') {
|
||||||
terminalTabRef.value!.acceptParams();
|
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(() => {
|
onMounted(() => {
|
||||||
|
loadTerminalSetting();
|
||||||
handleChange('terminal');
|
handleChange('terminal');
|
||||||
});
|
});
|
||||||
onUnmounted(() => {
|
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