Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
@@ -15,6 +15,7 @@ export interface HostConnectLogQueryRequest extends Pagination {
|
||||
token?: string;
|
||||
status?: string;
|
||||
startTimeRange?: string[];
|
||||
clearLimit?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface ExecLogQueryRequest extends Pagination {
|
||||
command?: string;
|
||||
status?: string;
|
||||
startTimeRange?: string[];
|
||||
clearLimit?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,6 +40,7 @@ export interface UploadTaskQueryRequest extends Pagination {
|
||||
description?: string;
|
||||
status?: string;
|
||||
createTimeRange?: string[];
|
||||
clearLimit?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
// 返回原始输出
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface OperatorLogQueryRequest extends Pagination {
|
||||
riskLevel?: string;
|
||||
result?: number;
|
||||
startTimeRange?: string[];
|
||||
clearLimit?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -222,7 +222,7 @@
|
||||
// 下载计划任务日志
|
||||
fileGetter = downloadExecJobLogFile(id);
|
||||
}
|
||||
// 瞎子啊
|
||||
// 下载
|
||||
const data = await fileGetter;
|
||||
downloadFile(data);
|
||||
};
|
||||
|
||||
6
orion-visor-ui/src/directive/focus/index.ts
Normal file
6
orion-visor-ui/src/directive/focus/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
mounted(el: HTMLElement) {
|
||||
// 聚焦元素
|
||||
el.focus();
|
||||
},
|
||||
};
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
|
||||
57
orion-visor-ui/src/store/modules/cache/index.ts
vendored
57
orion-visor-ui/src/store/modules/cache/index.ts
vendored
@@ -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, {});
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -12,6 +12,9 @@ export const HostConnectStatus = {
|
||||
FORCE_OFFLINE: 'FORCE_OFFLINE',
|
||||
};
|
||||
|
||||
// 清理数量
|
||||
export const clearLimit = 2000;
|
||||
|
||||
// 主机连接状态 字典项
|
||||
export const connectStatusKey = 'hostConnectStatus';
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
// 清理数量
|
||||
export const clearLimit = 1000;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -26,6 +26,9 @@ export const UploadTaskFileStatus = {
|
||||
CANCELED: 'CANCELED',
|
||||
};
|
||||
|
||||
// 清理数量
|
||||
export const clearLimit = 2000;
|
||||
|
||||
// 上传任务状态 字典项
|
||||
export const uploadTaskStatusKey = 'uploadTaskStatus';
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
// 清理数量
|
||||
export const clearLimit = 1000;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -27,6 +27,9 @@ export const getLogDetail = (record: OperatorLogQueryResponse): Record<string, a
|
||||
}
|
||||
};
|
||||
|
||||
// 清理数量
|
||||
export const clearLimit = 2000;
|
||||
|
||||
// 操作日志模块 字典项
|
||||
export const operatorLogModuleKey = 'operatorLogModule';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user