Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
lijiahangmax
2024-08-29 00:23:33 +08:00
55 changed files with 368 additions and 92 deletions

View File

@@ -15,6 +15,7 @@ export interface HostConnectLogQueryRequest extends Pagination {
token?: string;
status?: string;
startTimeRange?: string[];
clearLimit?: number;
}
/**

View File

@@ -13,6 +13,7 @@ export interface ExecLogQueryRequest extends Pagination {
command?: string;
status?: string;
startTimeRange?: string[];
clearLimit?: number;
}
/**

View File

@@ -40,6 +40,7 @@ export interface UploadTaskQueryRequest extends Pagination {
description?: string;
status?: string;
createTimeRange?: string[];
clearLimit?: number;
}
/**

View File

@@ -27,8 +27,7 @@ export function getSystemAppInfo() {
* 获取仓库最后版本信息
*/
export function getRepoLatestRelease() {
// return axios.get<RepoReleaseResponse>('https://gitee.com/api/v5/repos/dromara/orion-visor/releases/latest', {
return axios.get<RepoReleaseResponse>('https://lijiahangmax.github.io/open-orion/orion-visor/releases-latest.json', {
return axios.get<RepoReleaseResponse>('https://visor.orionsec.cn/releases-latest.json', {
// 不添加请求头 否则会报 401
setAuthorization: false,
// 返回原始输出

View File

@@ -13,6 +13,7 @@ export interface OperatorLogQueryRequest extends Pagination {
riskLevel?: string;
result?: number;
startTimeRange?: string[];
clearLimit?: number;
}
/**

View File

@@ -4,7 +4,7 @@
<a-space size="large">
<a-link target="_blank" href="https://github.com/dromara/orion-visor">github</a-link>
<a-link target="_blank" href="https://gitee.com/dromara/orion-visor">gitee</a-link>
<a-link target="_blank" href="https://lijiahangmax.github.io/open-orion/orion-visor">文档</a-link>
<a-link target="_blank" href="https://visor.orionsec.cn">文档</a-link>
<a-link target="_blank" href="https://github.com/dromara/orion-visor/blob/main/LICENSE">License</a-link>
<a-link target="_blank" :href="`https://github.com/dromara/orion-visor/releases/tag/v${version}`">v{{ version }} {{ release }}</a-link>
</a-space>

View File

@@ -222,7 +222,7 @@
// 下载计划任务日志
fileGetter = downloadExecJobLogFile(id);
}
// 瞎子啊
// 下载
const data = await fileGetter;
downloadFile(data);
};

View File

@@ -0,0 +1,6 @@
export default {
mounted(el: HTMLElement) {
// 聚焦元素
el.focus();
},
};

View File

@@ -1,8 +1,10 @@
import type { App } from 'vue';
import permission from './permission';
import focus from './focus';
export default {
install(Vue: App) {
Vue.directive('permission', permission);
Vue.directive('focus', focus);
},
};

View File

@@ -6,6 +6,7 @@ import type { HostType } from '@/api/asset/host';
import { getHostList } from '@/api/asset/host';
import type { PreferenceType } from '@/api/user/preference';
import { getPreference } from '@/api/user/preference';
import usePermission from '@/hooks/permission';
import { defineStore } from 'pinia';
import { getUserList } from '@/api/user/user';
import { getRoleList } from '@/api/user/role';
@@ -43,7 +44,25 @@ export default defineStore('cache', {
},
// 加载数据
async load<T>(name: CacheType, loader: () => Promise<AxiosResponse<T>>, force = false, onErrorValue = []) {
async load<T>(name: CacheType,
loader: () => Promise<AxiosResponse<T>>,
permissions: Array<string> | undefined = undefined,
force = false,
onErrorValue: any = []) {
// 权限检查
const len = permissions?.length;
if (len) {
const { hasPermission, hasAnyPermission } = usePermission();
if (len === 1) {
if (!hasPermission(permissions[0])) {
return onErrorValue as T;
}
} else {
if (!hasAnyPermission(permissions)) {
return onErrorValue as T;
}
}
}
// 尝试直接从缓存中获取数据
if (this[name] && !force) {
return this[name] as T;
@@ -60,92 +79,92 @@ export default defineStore('cache', {
// 获取用户列表
async loadUsers(force = false) {
return await this.load('users', getUserList, force);
return await this.load('users', getUserList, ['infra:system-user:query'], force);
},
// 获取角色列表
async loadRoles(force = false) {
return await this.load('roles', getRoleList, force);
return await this.load('roles', getRoleList, ['infra:system-role:query'], force);
},
// 获取菜单列表
async loadMenus(force = false) {
return await this.load('menus', () => getMenuList({}), force);
return await this.load('menus', () => getMenuList({}), ['infra:system-menu:query'], force);
},
// 获取主机分组列表
async loadHostGroups(force = false) {
return await this.load('hostGroups', getHostGroupTree, force);
return await this.load('hostGroups', getHostGroupTree, ['asset:host-group:update'], force);
},
// 获取主机列表
async loadHosts(type: HostType, force = false) {
return await this.load(`host_${type}`, () => getHostList(type), force);
return await this.load(`host_${type}`, () => getHostList(type), ['asset:host:query'], force);
},
// 获取主机密钥列表
async loadHostKeys(force = false) {
return await this.load('hostKeys', getHostKeyList, force);
return await this.load('hostKeys', getHostKeyList, ['asset:host-key:query'], force);
},
// 获取主机身份列表
async loadHostIdentities(force = false) {
return await this.load('hostIdentities', getHostIdentityList, force);
return await this.load('hostIdentities', getHostIdentityList, ['asset:host-identity:query'], force);
},
// 获取字典配置项列表
async loadDictKeys(force = false) {
return await this.load('dictKeys', getDictKeyList, force);
return await this.load('dictKeys', getDictKeyList, undefined, force);
},
// 加载 tags
async loadTags(type: TagType, force = false) {
return await this.load(`${type}_Tags`, () => getTagList(type), force);
return await this.load(`${type}_Tags`, () => getTagList(type), undefined, force);
},
// 获取已授权的主机密钥列表
async loadAuthorizedHostKeys(force = false) {
return await this.load('authorizedHostKeys', getCurrentAuthorizedHostKey, force);
return await this.load('authorizedHostKeys', getCurrentAuthorizedHostKey, undefined, force);
},
// 获取已授权的主机身份列表
async loadAuthorizedHostIdentities(force = false) {
return await this.load('authorizedHostIdentities', getCurrentAuthorizedHostIdentity, force);
return await this.load('authorizedHostIdentities', getCurrentAuthorizedHostIdentity, undefined, force);
},
// 获取命令片段分组
async loadCommandSnippetGroups(force = false) {
return await this.load('commandSnippetGroups', getCommandSnippetGroupList, force);
return await this.load('commandSnippetGroups', getCommandSnippetGroupList, undefined, force);
},
// 获取路径书签分组
async loadPathBookmarkGroups(force = false) {
return await this.load('pathBookmarkGroups', getPathBookmarkGroupList, force);
return await this.load('pathBookmarkGroups', getPathBookmarkGroupList, undefined, force);
},
// 获取命令片段列表
async loadCommandSnippets(force = false) {
return await this.load('commandSnippets', getCommandSnippetList, force);
return await this.load('commandSnippets', getCommandSnippetList, undefined, force, {});
},
// 获取路径书签列表
async loadPathBookmarks(force = false) {
return await this.load('pathBookmarks', getPathBookmarkList, force);
return await this.load('pathBookmarks', getPathBookmarkList, undefined, force, {});
},
// 获取执行计划列表
async loadExecJobs(force = false) {
return await this.load('execJob', getExecJobList, force);
return await this.load('execJob', getExecJobList, ['asset:exec-job:query'], force);
},
// 加载偏好
async loadPreference<T>(type: PreferenceType, force = false) {
return await this.load(`preference_${type}`, () => getPreference<T>(type), force);
return await this.load(`preference_${type}`, () => getPreference<T>(type), undefined, force, {});
},
// 加载偏好项
async loadPreferenceItem<T>(type: PreferenceType, item: string, force = false) {
return await this.load(`preference_${type}_${item}`, () => getPreference<T>(type, [item]), force);
return await this.load(`preference_${type}_${item}`, () => getPreference<T>(type, [item]), undefined, force, {});
},
}

View File

@@ -55,6 +55,15 @@
:options="toOptions(connectTypeKey)"
allow-clear />
</a-form-item>
<!-- 清理数量 -->
<a-form-item field="clearLimit" label="清理数量">
<a-input-number v-model="formModel.clearLimit"
:min="1"
:max="clearLimit"
:placeholder="`请输入最大清理数量 最大: ${clearLimit}`"
hide-button
allow-clear />
</a-form-item>
</a-form>
</a-spin>
</a-modal>
@@ -71,7 +80,7 @@
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible';
import { connectStatusKey, connectTypeKey } from '../types/const';
import { connectStatusKey, connectTypeKey, clearLimit } from '../types/const';
import { getHostConnectLogCount, clearHostConnectLog } from '@/api/asset/host-connect-log';
import { Message, Modal } from '@arco-design/web-vue';
import { useDictStore } from '@/store';
@@ -92,6 +101,7 @@
type: undefined,
status: undefined,
startTimeRange: undefined,
clearLimit,
};
};
@@ -112,6 +122,10 @@
// 确定
const handlerOk = async () => {
if (!formModel.value.clearLimit) {
Message.error('请输入清理数量');
return false;
}
setLoading(true);
try {
// 获取总数量
@@ -134,7 +148,7 @@
const doClear = (count: number) => {
Modal.confirm({
title: '删除清空',
content: `确定要删除 ${count} 条数据吗? 确定后将立即删除且无法恢复!`,
content: `确定要删除 ${Math.min(count, formModel.value.clearLimit || 0)} 条数据吗? 确定后将立即删除且无法恢复!`,
onOk: async () => {
setLoading(true);
try {

View File

@@ -12,6 +12,9 @@ export const HostConnectStatus = {
FORCE_OFFLINE: 'FORCE_OFFLINE',
};
// 清理数量
export const clearLimit = 2000;
// 主机连接状态 字典项
export const connectStatusKey = 'hostConnectStatus';

View File

@@ -17,10 +17,10 @@
<a-link target="_blank" href="https://github.com/dromara/orion-visor/issues">上报 bug</a-link>
</a-col>
<a-col :span="12">
<a-link target="_blank" href="https://lijiahangmax.github.io/open-orion/orion-visor/operator/asset.html">操作手册</a-link>
<a-link target="_blank" href="https://visor.orionsec.cn/operator/asset.html">操作手册</a-link>
</a-col>
<a-col :span="12">
<a-link target="_blank" href="https://lijiahangmax.github.io/open-orion/orion-visor/update/change-log.html">更新日志</a-link>
<a-link target="_blank" href="https://visor.orionsec.cn/update/change-log.html">更新日志</a-link>
</a-col>
</a-row>
</a-card>

View File

@@ -48,6 +48,15 @@
:options="toOptions(execStatusKey)"
placeholder="请选择执行状态" />
</a-form-item>
<!-- 清理数量 -->
<a-form-item field="clearLimit" label="清理数量">
<a-input-number v-model="formModel.clearLimit"
:min="1"
:max="clearLimit"
:placeholder="`请输入最大清理数量 最大: ${clearLimit}`"
hide-button
allow-clear />
</a-form-item>
</a-form>
</a-spin>
</a-modal>
@@ -68,6 +77,7 @@
import { getExecCommandLogCount, clearExecCommandLog } from '@/api/exec/exec-command-log';
import { Message, Modal } from '@arco-design/web-vue';
import { useDictStore } from '@/store';
import { clearLimit } from '../types/const';
import UserSelector from '@/components/user/user/selector/index.vue';
const emits = defineEmits(['clear']);
@@ -86,7 +96,8 @@
description: undefined,
command: undefined,
status: undefined,
startTimeRange: undefined
startTimeRange: undefined,
clearLimit,
};
};
@@ -105,6 +116,10 @@
// 确定
const handlerOk = async () => {
if (!formModel.value.clearLimit) {
Message.error('请输入清理数量');
return false;
}
setLoading(true);
try {
// 获取总数量
@@ -127,7 +142,7 @@
const doClear = (count: number) => {
Modal.confirm({
title: '删除清空',
content: `确定要删除 ${count} 条数据吗? 确定后将立即删除且无法恢复!`,
content: `确定要删除 ${Math.min(count, formModel.value.clearLimit || 0)} 条数据吗? 确定后将立即删除且无法恢复!`,
onOk: async () => {
setLoading(true);
try {

View File

@@ -0,0 +1,2 @@
// 清理数量
export const clearLimit = 1000;

View File

@@ -49,6 +49,15 @@
placeholder="请选择状态"
allow-clear />
</a-form-item>
<!-- 清理数量 -->
<a-form-item field="clearLimit" label="清理数量">
<a-input-number v-model="formModel.clearLimit"
:min="1"
:max="clearLimit"
:placeholder="`请输入最大清理数量 最大: ${clearLimit}`"
hide-button
allow-clear />
</a-form-item>
</a-form>
</a-spin>
</a-modal>
@@ -65,7 +74,7 @@
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible';
import { uploadTaskStatusKey } from '../types/const';
import { clearLimit, uploadTaskStatusKey } from '../types/const';
import { getUploadTaskCount, clearUploadTask } from '@/api/exec/upload-task';
import { Message, Modal } from '@arco-design/web-vue';
import { useDictStore } from '@/store';
@@ -105,6 +114,10 @@
// 确定
const handlerOk = async () => {
if (!formModel.value.clearLimit) {
Message.error('请输入清理数量');
return false;
}
setLoading(true);
try {
// 获取总数量
@@ -127,7 +140,7 @@
const doClear = (count: number) => {
Modal.confirm({
title: '删除清空',
content: `确定要删除 ${count} 条数据吗? 确定后将立即删除且无法恢复!`,
content: `确定要删除 ${Math.min(count, formModel.value.clearLimit || 0)} 条数据吗? 确定后将立即删除且无法恢复!`,
onOk: async () => {
setLoading(true);
try {

View File

@@ -26,6 +26,9 @@ export const UploadTaskFileStatus = {
CANCELED: 'CANCELED',
};
// 清理数量
export const clearLimit = 2000;
// 上传任务状态 字典项
export const uploadTaskStatusKey = 'uploadTaskStatus';

View File

@@ -43,6 +43,15 @@
:options="toOptions(execStatusKey)"
placeholder="请选择执行状态" />
</a-form-item>
<!-- 清理数量 -->
<a-form-item field="clearLimit" label="清理数量">
<a-input-number v-model="formModel.clearLimit"
:min="1"
:max="clearLimit"
:placeholder="`请输入最大清理数量 最大: ${clearLimit}`"
hide-button
allow-clear />
</a-form-item>
</a-form>
</a-spin>
</a-modal>
@@ -63,6 +72,7 @@
import { getExecJobLogCount, clearExecJobLog } from '@/api/job/exec-job-log';
import { Message, Modal } from '@arco-design/web-vue';
import { useDictStore } from '@/store';
import { clearLimit } from '../types/const';
import ExecJobSelector from '@/components/exec/job/selector/index.vue';
const emits = defineEmits(['clear']);
@@ -81,7 +91,8 @@
description: undefined,
command: undefined,
status: undefined,
startTimeRange: undefined
startTimeRange: undefined,
clearLimit,
};
};
@@ -100,6 +111,10 @@
// 确定
const handlerOk = async () => {
if (!formModel.value.clearLimit) {
Message.error('请输入清理数量');
return false;
}
setLoading(true);
try {
// 获取总数量
@@ -122,7 +137,7 @@
const doClear = (count: number) => {
Modal.confirm({
title: '删除清空',
content: `确定要删除 ${count} 条数据吗? 确定后将立即删除且无法恢复!`,
content: `确定要删除 ${Math.min(count, formModel.value.clearLimit || 0)} 条数据吗? 确定后将立即删除且无法恢复!`,
onOk: async () => {
setLoading(true);
try {

View File

@@ -0,0 +1,2 @@
// 清理数量
export const clearLimit = 1000;

View File

@@ -61,6 +61,15 @@
placeholder="请选择执行结果"
allow-clear />
</a-form-item>
<!-- 清理数量 -->
<a-form-item field="clearLimit" label="清理数量">
<a-input-number v-model="formModel.clearLimit"
:min="1"
:max="clearLimit"
:placeholder="`请输入最大清理数量 最大: ${clearLimit}`"
hide-button
allow-clear />
</a-form-item>
</a-form>
</a-spin>
</a-modal>
@@ -81,7 +90,7 @@
import { getOperatorLogCount, clearOperatorLog } from '@/api/user/operator-log';
import { Message, Modal } from '@arco-design/web-vue';
import { useDictStore } from '@/store';
import { operatorLogModuleKey, operatorLogResultKey, operatorLogTypeKey, operatorRiskLevelKey } from '@/views/user/operator-log/types/const';
import { operatorLogModuleKey, operatorLogResultKey, operatorLogTypeKey, operatorRiskLevelKey, clearLimit } from '../types/const';
import { labelFilter } from '@/types/form';
import UserSelector from '@/components/user/user/selector/index.vue';
@@ -96,6 +105,7 @@
riskLevel: undefined,
result: undefined,
startTimeRange: undefined,
clearLimit,
};
};
@@ -136,6 +146,10 @@
// 确定
const handlerOk = async () => {
if (!formModel.value.clearLimit) {
Message.error('请输入清理数量');
return false;
}
setLoading(true);
try {
// 获取总数量
@@ -158,7 +172,7 @@
const doClear = (count: number) => {
Modal.confirm({
title: '删除清空',
content: `确定要删除 ${count} 条数据吗? 确定后将立即删除且无法恢复!`,
content: `确定要删除 ${Math.min(count, formModel.value.clearLimit || 0)} 条数据吗? 确定后将立即删除且无法恢复!`,
onOk: async () => {
setLoading(true);
try {

View File

@@ -27,6 +27,9 @@ export const getLogDetail = (record: OperatorLogQueryResponse): Record<string, a
}
};
// 清理数量
export const clearLimit = 2000;
// 操作日志模块 字典项
export const operatorLogModuleKey = 'operatorLogModule';