路径标签.

This commit is contained in:
lijiahang
2024-04-24 16:43:59 +08:00
parent 8ecb5a687e
commit d6a021b4d9
25 changed files with 188 additions and 68 deletions

View File

@@ -42,6 +42,10 @@ public class PathBookmarkDO extends BaseDO {
@TableField("name")
private String name;
@Schema(description = "类型")
@TableField("type")
private String type;
@Schema(description = "路径")
@TableField("path")
private String path;

View File

@@ -34,6 +34,9 @@ public class PathBookmarkCacheDTO implements LongCacheIdModel, Serializable {
@Schema(description = "名称")
private String name;
@Schema(description = "类型")
private String type;
@Schema(description = "路径")
private String path;

View File

@@ -34,6 +34,11 @@ public class PathBookmarkCreateRequest implements Serializable {
@Schema(description = "名称")
private String name;
@NotBlank
@Size(max = 4)
@Schema(description = "类型")
private String type;
@NotBlank
@Size(max = 1024)
@Schema(description = "路径")

View File

@@ -39,6 +39,11 @@ public class PathBookmarkUpdateRequest implements Serializable {
@Schema(description = "名称")
private String name;
@NotBlank
@Size(max = 4)
@Schema(description = "类型")
private String type;
@NotBlank
@Size(max = 1024)
@Schema(description = "路径")

View File

@@ -36,6 +36,9 @@ public class PathBookmarkVO implements Serializable {
@Schema(description = "名称")
private String name;
@Schema(description = "类型")
private String type;
@Schema(description = "路径")
private String path;

View File

@@ -8,6 +8,7 @@
<result column="user_id" property="userId"/>
<result column="group_id" property="groupId"/>
<result column="name" property="name"/>
<result column="type" property="type"/>
<result column="path" property="path"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
@@ -18,7 +19,7 @@
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, user_id, group_id, name, path, create_time, update_time, creator, updater, deleted
id, user_id, group_id, name, type, path, create_time, update_time, creator, updater, deleted
</sql>
</mapper>

View File

@@ -36,4 +36,7 @@ public class DictValueQueryRequest extends PageRequest {
@Schema(description = "配置描述")
private String label;
@Schema(description = "额外参数")
private String extra;
}

View File

@@ -374,6 +374,7 @@ public class DictValueServiceImpl implements DictValueService {
.like(DictValueDO::getKeyName, request.getKeyName())
.like(DictValueDO::getValue, request.getValue())
.like(DictValueDO::getLabel, request.getLabel())
.like(DictValueDO::getExtra, request.getExtra())
.orderByDesc(DictValueDO::getId);
}

View File

@@ -7,6 +7,7 @@ import axios from 'axios';
export interface PathBookmarkCreateRequest {
groupId?: number;
name?: string;
type?: string;
path?: string;
}
@@ -24,6 +25,7 @@ export interface PathBookmarkQueryResponse extends PathBookmarkQueryResponseExtr
id: number;
groupId: number;
name: string;
type: string;
path: string;
}

View File

@@ -34,14 +34,11 @@ export interface DictValueRollbackRequest {
* 字典配置值查询请求
*/
export interface DictValueQueryRequest extends Pagination {
searchValue?: string;
id?: number;
keyId?: number;
keyName?: string;
value?: string;
label?: string;
extra?: string;
sort?: number;
}
/**

View File

@@ -8,7 +8,7 @@ import type {
TerminalShortcutSetting,
TerminalState
} from './types';
import type { ISftpSession, ISshSession, PanelSessionTabType, TerminalPanelTabItem } from '@/views/host/terminal/types/terminal.type';
import type { ITerminalSession, PanelSessionTabType, TerminalPanelTabItem } from '@/views/host/terminal/types/terminal.type';
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
import { getCurrentAuthorizedHost } from '@/api/asset/asset-authorized-data';
import type { HostQueryResponse } from '@/api/asset/host';
@@ -18,7 +18,7 @@ import { defineStore } from 'pinia';
import { getPreference, updatePreference } from '@/api/user/preference';
import { nextId } from '@/utils';
import { Message } from '@arco-design/web-vue';
import { PanelSessionType, TerminalTabs } from '@/views/host/terminal/types/terminal.const';
import { TerminalTabs } from '@/views/host/terminal/types/terminal.const';
import TerminalTabManager from '@/views/host/terminal/handler/terminal-tab-manager';
import TerminalSessionManager from '@/views/host/terminal/handler/terminal-session-manager';
import TerminalPanelManager from '@/views/host/terminal/handler/terminal-panel-manager';
@@ -195,8 +195,30 @@ export default defineStore('terminal', {
}
},
// 检查当前是否为终端页面 并且获取当前 ssh 会话
getAndCheckCurrentSshSession(tips: boolean = true) {
// 获取当前会话类型
getCurrentSessionType(tips: boolean = false) {
// 获取当前 activeTab
const activeTab = this.tabManager.active;
if (activeTab !== TerminalTabs.TERMINAL_PANEL.key) {
if (tips) {
Message.warning('请切换到终端标签页');
}
return;
}
// 获取面板会话
const type = this.panelManager
.getCurrentPanel()
.getCurrentTab()
?.type;
if (!type && tips) {
Message.warning(`请打开 ${type}`);
return;
}
return type;
},
// 获取当前会话
getCurrentSession<T extends ITerminalSession>(type: string, tips: boolean = false) {
// 获取当前 activeTab
const activeTab = this.tabManager.active;
if (activeTab !== TerminalTabs.TERMINAL_PANEL.key) {
@@ -206,37 +228,24 @@ export default defineStore('terminal', {
return;
}
// 获取当前会话
const session = this.getCurrentSshSession();
const session = this._getCurrentSession<T>(type);
if (!session && tips) {
Message.warning('请打开终端');
Message.warning(`请打开 ${type}`);
}
return session;
},
// 获取当前 ssh 会话
getCurrentSshSession() {
// 获取当前会话
_getCurrentSession<T extends ITerminalSession>(type: string): T | undefined {
// 获取面板会话
const sessionTab = this.panelManager
.getCurrentPanel()
.getCurrentTab();
if (!sessionTab || sessionTab.type !== PanelSessionType.SSH.type) {
if (!sessionTab || sessionTab.type !== type) {
return;
}
// 获取会话
return this.sessionManager.getSession<ISshSession>(sessionTab.sessionId);
},
// 获取当前 sftp 会话
getCurrentSftpSession() {
// 获取面板会话
const sessionTab = this.panelManager
.getCurrentPanel()
.getCurrentTab();
if (!sessionTab || sessionTab.type !== PanelSessionType.SFTP.type) {
return;
}
// 获取会话
return this.sessionManager.getSession<ISftpSession>(sessionTab.sessionId);
return this.sessionManager.getSession<T>(sessionTab.sessionId);
},
},

View File

@@ -114,6 +114,12 @@
{{ record.hostAddress }}
</span>
</template>
<!-- 类型 -->
<template #type="{ record }">
<a-tag :color="getDictValue(connectTypeKey, record.type, 'color')">
{{ getDictValue(connectTypeKey, record.type) }}
</a-tag>
</template>
<!-- 状态 -->
<template #status="{ record }">
<span class="circle" :style="{

View File

@@ -25,7 +25,7 @@ const columns = [
title: '状态',
dataIndex: 'status',
slotName: 'status',
align: 'left',
align: 'center',
width: 106,
}, {
title: '留痕地址',

View File

@@ -78,6 +78,7 @@
</script>
<script lang="ts" setup>
import type { ISshSession } from '@/views/host/terminal/types/terminal.type';
import type { CommandSnippetWrapperResponse, CommandSnippetQueryResponse } from '@/api/asset/command-snippet';
import { ref, provide } from 'vue';
import useVisible from '@/hooks/visible';
@@ -85,13 +86,14 @@
import { deleteCommandSnippet, getCommandSnippetList } from '@/api/asset/command-snippet';
import { useCacheStore, useTerminalStore } from '@/store';
import { openUpdateSnippetKey, removeSnippetKey } from '../types/const';
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const';
import CommandSnippetListItem from './command-snippet-list-item.vue';
import CommandSnippetListGroup from './command-snippet-list-group.vue';
import CommandSnippetFormDrawer from './command-snippet-form-drawer.vue';
const { loading, setLoading } = useLoading();
const { visible, setVisible } = useVisible();
const { getCurrentSshSession } = useTerminalStore();
const { getCurrentSession } = useTerminalStore();
const cacheStore = useCacheStore();
const formDrawer = ref();
@@ -274,8 +276,8 @@
// 关闭回调
const onClose = () => {
// 聚焦终端
getCurrentSshSession()?.focus();
// 关闭时候如果打开的是终端 则聚焦终端
getCurrentSession<ISshSession>(PanelSessionType.SSH.type)?.focus();
};
</script>

View File

@@ -113,18 +113,20 @@
</script>
<script lang="ts" setup>
import type { ISshSession } from '@/views/host/terminal/types/terminal.type';
import type { CommandSnippetQueryResponse } from '@/api/asset/command-snippet';
import { useTerminalStore } from '@/store';
import { useDebounceFn } from '@vueuse/core';
import { copy } from '@/hooks/copy';
import { inject } from 'vue';
import { openUpdateSnippetKey, removeSnippetKey } from '../types/const';
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const';
const props = defineProps<{
item: CommandSnippetQueryResponse;
}>();
const { getAndCheckCurrentSshSession } = useTerminalStore();
const { getCurrentSession } = useTerminalStore();
let clickCount = 0;
@@ -137,9 +139,11 @@
// 点击命令
const clickItem = () => {
if (++clickCount == 2) {
// 双击执行
clickCount = 0;
exec();
} else {
// 单击展开
expandItem();
}
};
@@ -183,7 +187,7 @@
// 写入命令
const write = (command: string) => {
const handler = getAndCheckCurrentSshSession()?.handler;
const handler = getCurrentSession<ISshSession>(PanelSessionType.SSH.type, true)?.handler;
if (handler && handler.enabledStatus('checkAppendMissing')) {
handler.checkAppendMissing(command);
}

View File

@@ -24,6 +24,13 @@
<a-form-item field="groupId" label="分组">
<path-bookmark-group-select v-model="formModel.groupId" />
</a-form-item>
<!-- 类型 -->
<a-form-item field="type" label="类型">
<a-select v-model="formModel.type"
:options="toOptions(pathBookmarkTypeKey)"
placeholder="请选择类型"
allow-clear />
</a-form-item>
<!-- 文件路径 -->
<a-form-item field="path" label="路径">
<a-textarea v-model="formModel.path"
@@ -49,11 +56,14 @@
import useVisible from '@/hooks/visible';
import { createPathBookmark, updatePathBookmark } from '@/api/asset/path-bookmark';
import formRules from '../types/form.rules';
import { PathBookmarkType, pathBookmarkTypeKey } from '../types/const';
import { useDictStore } from '@/store';
import { Message } from '@arco-design/web-vue';
import PathBookmarkGroupSelect from './path-bookmark-group-select.vue';
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();
const { toOptions } = useDictStore();
const title = ref<string>();
const isAddHandle = ref<boolean>(true);
@@ -63,6 +73,7 @@
id: undefined,
groupId: undefined,
name: undefined,
type: PathBookmarkType.DIR,
path: undefined,
};
};

View File

@@ -78,20 +78,22 @@
</script>
<script lang="ts" setup>
import { ISshSession } from '@/views/host/terminal/types/terminal.type';
import type { PathBookmarkWrapperResponse, PathBookmarkQueryResponse } from '@/api/asset/path-bookmark';
import { ref, provide } from 'vue';
import { ref, provide, onMounted } from 'vue';
import useVisible from '@/hooks/visible';
import useLoading from '@/hooks/loading';
import { deletePathBookmark, getPathBookmarkList } from '@/api/asset/path-bookmark';
import { useCacheStore, useTerminalStore } from '@/store';
import { openUpdatePathKey, removePathKey } from '../types/const';
import { useCacheStore, useDictStore, useTerminalStore } from '@/store';
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const';
import { dictKeys, openUpdatePathKey, removePathKey } from '../types/const';
import PathBookmarkListItem from './path-bookmark-list-item.vue';
import PathBookmarkListGroup from './path-bookmark-list-group.vue';
import PathBookmarkFormDrawer from './path-bookmark-form-drawer.vue';
const { loading, setLoading } = useLoading();
const { visible, setVisible } = useVisible();
const { getCurrentSshSession } = useTerminalStore();
const { getCurrentSession } = useTerminalStore();
const cacheStore = useCacheStore();
const formDrawer = ref();
@@ -274,10 +276,15 @@
// 关闭回调
const onClose = () => {
// 聚焦终端
getCurrentSshSession()?.focus();
// 关闭时候如果打开的是终端 则聚焦终端
getCurrentSession<ISshSession>(PanelSessionType.SSH.type)?.focus();
};
// 加载字典值
onMounted(() => {
useDictStore().loadKeys(dictKeys);
});
</script>
<style lang="less" scoped>

View File

@@ -28,16 +28,16 @@
</template>
进入
</a-tag>
<!-- 粘贴 -->
<!-- 复制 -->
<a-tag class="pointer usn"
size="small"
:checkable="true"
:checked="true"
@click.stop.prevent="paste">
@click.stop.prevent="copyPath">
<template #icon>
<icon-paste />
<icon-copy />
</template>
粘贴
复制
</a-tag>
</a-space>
</div>
@@ -50,12 +50,12 @@
</div>
<!-- 右键菜单 -->
<template #content>
<!-- 进入父目录 -->
<!-- 进入 -->
<a-doption @click="changePath">
<div class="terminal-context-menu-icon">
<icon-link />
</div>
<div>进入父目录</div>
<div>进入</div>
</a-doption>
<!-- 复制 -->
<a-doption @click="copyPath">
@@ -112,19 +112,21 @@
</script>
<script lang="ts" setup>
import type { ISftpSession, ISshSession } from '@/views/host/terminal/types/terminal.type';
import type { PathBookmarkQueryResponse } from '@/api/asset/path-bookmark';
import { useTerminalStore } from '@/store';
import { useDebounceFn } from '@vueuse/core';
import { copy } from '@/hooks/copy';
import { inject } from 'vue';
import { openUpdatePathKey, removePathKey } from '../types/const';
import { getParentPath } from '@/utils/file';
import { openUpdatePathKey, PathBookmarkType, removePathKey } from '../types/const';
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const';
const props = defineProps<{
item: PathBookmarkQueryResponse;
}>();
const { getAndCheckCurrentSshSession } = useTerminalStore();
const { getCurrentSession, getCurrentSessionType } = useTerminalStore();
let clickCount = 0;
@@ -138,8 +140,17 @@
const clickItem = () => {
if (++clickCount == 2) {
clickCount = 0;
changePath();
// 双击
const type = getCurrentSessionType(true);
if (type === PanelSessionType.SSH.type) {
// SSH 粘贴
paste();
} else if (type === PanelSessionType.SFTP.type) {
// SFTP 切换目录
listFiles();
}
} else {
// 单击展开
expandItem();
}
};
@@ -173,18 +184,37 @@
// 粘贴
const paste = () => {
write(props.item.path);
writeCommand(props.item.path);
};
// 进入父目录
// 切换目录
const changePath = () => {
const parentPath = getParentPath(props.item.path);
write( parentPath+ '\r\n');
const type = getCurrentSessionType(true);
if (type === PanelSessionType.SSH.type) {
const path = props.item.type === PathBookmarkType.DIR
? props.item.path
: getParentPath(props.item.path);
// SSH cd
writeCommand('cd ' + path + '\r\n');
} else if (type === PanelSessionType.SFTP.type) {
// SFTP 切换目录
listFiles();
}
};
// 写入路径
const write = (command: string) => {
const handler = getAndCheckCurrentSshSession()?.handler;
// 查询 sftp 文件列表
const listFiles = () => {
// 如果非文件夹则查询父文件夹
const path = props.item.type === PathBookmarkType.DIR
? props.item.path
: getParentPath(props.item.path);
// 查询列表
getCurrentSession<ISftpSession>(PanelSessionType.SFTP.type, true)?.list(path);
};
// 写入 ssh 命令
const writeCommand = (command: string) => {
const handler = getCurrentSession<ISshSession>(PanelSessionType.SSH.type, true)?.handler;
if (handler && handler.enabledStatus('checkAppendMissing')) {
handler.checkAppendMissing(command);
}

View File

@@ -1,5 +1,17 @@
// 路径书签类型
export const PathBookmarkType = {
FILE: 'FILE',
DIR: 'DIR',
};
// 打开 updatePath key
export const openUpdatePathKey = Symbol();
// 删除 path key
export const removePathKey = Symbol();
// 路径书签类型 字典项
export const pathBookmarkTypeKey = 'pathBookmarkType';
// 加载的字典值
export const dictKeys = [pathBookmarkTypeKey];

View File

@@ -12,13 +12,22 @@ export const name = [{
message: '名称长度不能大于64位'
}] as FieldRule[];
export const type = [{
required: true,
message: '请选择类型'
}] as FieldRule[];
export const path = [{
required: true,
message: '请输入路径'
}, {
maxLength: 1000,
message: '名称长度不能大于1000位'
}] as FieldRule[];
export default {
groupId,
name,
type,
path,
} as Record<string, FieldRule | FieldRule[]>;

View File

@@ -33,7 +33,8 @@
</script>
<script lang="ts" setup>
import { TerminalTabs, TerminalShortcutKeys } from '../../types/terminal.const';
import type { ISshSession } from '../../types/terminal.type';
import { TerminalTabs, TerminalShortcutKeys, PanelSessionType } from '../../types/terminal.const';
import { useTerminalStore } from '@/store';
import { onMounted, onUnmounted, watch } from 'vue';
import { addEventListen, removeEventListen } from '@/utils/event';
@@ -45,17 +46,17 @@
import TerminalShortcutSetting from '../setting/shortcut/terminal-shortcut-setting.vue';
import TerminalPanelsView from '@/views/host/terminal/components/layout/terminal-panels-view.vue';
const { preference, tabManager, getCurrentSshSession } = useTerminalStore();
const { preference, tabManager, getCurrentSession } = useTerminalStore();
// 监听 tab 切换
watch(() => tabManager.active, (active, before) => {
// 失焦 tab
if (before === TerminalTabs.TERMINAL_PANEL.key) {
getCurrentSshSession()?.blur();
getCurrentSession<ISshSession>(PanelSessionType.SSH.type)?.blur();
}
// 聚焦 tab
if (active === TerminalTabs.TERMINAL_PANEL.key) {
getCurrentSshSession()?.focus();
getCurrentSession<ISshSession>(PanelSessionType.SSH.type)?.focus();
}
// 修改标题
document.title = Object.values(TerminalTabs)

View File

@@ -24,15 +24,16 @@
</script>
<script lang="ts" setup>
import type { SidebarAction } from '../../types/terminal.type';
import type { ISshSession, SidebarAction } from '../../types/terminal.type';
import { useTerminalStore } from '@/store';
import { ref } from 'vue';
import { PanelSessionType } from '../../types/terminal.const';
import IconActions from './icon-actions.vue';
import CommandSnippetListDrawer from '../../../command-snippet/components/command-snippet-list-drawer.vue';
import PathBookmarkListDrawer from '../../../path-bookmark/components/path-bookmark-list-drawer.vue';
import CommandSnippetListDrawer from '@/views/host/command-snippet/components/command-snippet-list-drawer.vue';
import PathBookmarkListDrawer from '@/views/host/path-bookmark/components/path-bookmark-list-drawer.vue';
import TransferDrawer from '@/views/host/terminal/components/transfer/transfer-drawer.vue';
const { getAndCheckCurrentSshSession } = useTerminalStore();
const { getCurrentSession } = useTerminalStore();
const snippetRef = ref();
const pathRef = ref();
@@ -69,7 +70,7 @@
// 终端截屏
const screenshot = () => {
const handler = getAndCheckCurrentSshSession()?.handler;
const handler = getCurrentSession<ISshSession>(PanelSessionType.SSH.type, true)?.handler;
if (handler && handler.enabledStatus('screenshot')) {
handler.screenshot();
}

View File

@@ -173,7 +173,6 @@
// 加载文件列表
const loadFiles = (path: string) => {
setTableLoading(true);
session.value?.list(path);
};

View File

@@ -50,6 +50,7 @@ export default class SftpSession implements ISftpSession {
// 查询文件列表
list(path: string) {
this.resolver.setLoading(true);
this.channel.send(InputProtocol.SFTP_LIST, {
sessionId: this.sessionId,
showHiddenFile: ~~this.showHiddenFile,

View File

@@ -13,13 +13,17 @@
allow-create
allow-clear />
</a-form-item>
<!-- 配置描述 -->
<a-form-item field="label" label="配置描述">
<a-input v-model="formModel.label" placeholder="请输入配置描述" allow-clear />
</a-form-item>
<!-- 配置值 -->
<a-form-item field="value" label="配置值">
<a-input v-model="formModel.value" placeholder="请输入配置值" allow-clear />
</a-form-item>
<!-- 配置描述 -->
<a-form-item field="label" label="配置描述">
<a-input v-model="formModel.label" placeholder="请输入配置描述" allow-clear />
<!-- 配置 -->
<a-form-item field="extra" label="额外参数">
<a-input v-model="formModel.extra" placeholder="额外参数" allow-clear />
</a-form-item>
</query-header>
</a-card>