mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 00:00:50 +00:00
fix: tighten frontend RBAC permission guards (#12717)
* fix: tighten frontend RBAC permission guards * feat: add permission directive coverage * refactor frontend global store usage
This commit is contained in:
committed by
zhengkunwang223
parent
7c7a59cf21
commit
75258bb465
+12
-12
@@ -6,7 +6,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, computed, ref, nextTick, provide } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||
import zhTw from 'element-plus/es/locale/lang/zh-tw';
|
||||
import en from 'element-plus/es/locale/lang/en';
|
||||
@@ -20,22 +20,22 @@ import esES from 'element-plus/es/locale/lang/es';
|
||||
import { useTheme } from '@/global/use-theme';
|
||||
useTheme();
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const { language } = useGlobalStore();
|
||||
const config = reactive({
|
||||
autoInsertSpace: false,
|
||||
});
|
||||
|
||||
const i18nLocale = computed(() => {
|
||||
if (globalStore.language === 'zh') return zhCn;
|
||||
if (globalStore.language === 'zh-Hant') return zhTw;
|
||||
if (globalStore.language === 'en') return en;
|
||||
if (globalStore.language === 'ja') return ja;
|
||||
if (globalStore.language === 'ms') return ms;
|
||||
if (globalStore.language === 'ru') return ru;
|
||||
if (globalStore.language === 'pt-BR') return ptBR;
|
||||
if (globalStore.language === 'ko') return ko;
|
||||
if (globalStore.language === 'tr') return tr;
|
||||
if (globalStore.language === 'es-ES') return esES;
|
||||
if (language.value === 'zh') return zhCn;
|
||||
if (language.value === 'zh-Hant') return zhTw;
|
||||
if (language.value === 'en') return en;
|
||||
if (language.value === 'ja') return ja;
|
||||
if (language.value === 'ms') return ms;
|
||||
if (language.value === 'ru') return ru;
|
||||
if (language.value === 'pt-BR') return ptBR;
|
||||
if (language.value === 'ko') return ko;
|
||||
if (language.value === 'tr') return tr;
|
||||
if (language.value === 'es-ES') return esES;
|
||||
return zhCn;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import i18n from '@/lang';
|
||||
import router from '@/routers';
|
||||
import { MsgError } from '@/utils/message';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
export const checkStatus = (status: number, msg: string): void => {
|
||||
const globalStore = GlobalStore();
|
||||
const { entrance, isLogin } = useGlobalStore();
|
||||
switch (status) {
|
||||
case 400:
|
||||
MsgError(msg ? msg : i18n.global.t('commons.res.paramError'));
|
||||
@@ -13,8 +13,8 @@ export const checkStatus = (status: number, msg: string): void => {
|
||||
MsgError(msg ? msg : i18n.global.t('commons.res.notFound'));
|
||||
break;
|
||||
case 403:
|
||||
globalStore.isLogin = false;
|
||||
router.replace({ name: 'entrance', params: { code: globalStore.entrance } });
|
||||
isLogin.value = false;
|
||||
router.replace({ name: 'entrance', params: { code: entrance.value } });
|
||||
MsgError(msg ? msg : i18n.global.t('commons.res.forbidden'));
|
||||
break;
|
||||
case 500:
|
||||
|
||||
+14
-20
@@ -3,7 +3,7 @@ import { ResultData } from '@/api/interface';
|
||||
import { ResultEnum } from '@/enums/http-enum';
|
||||
import { checkStatus } from './helper/check-status';
|
||||
import router from '@/routers';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { MsgError } from '@/utils/message';
|
||||
import { encodeBase64 } from '@/utils/base64';
|
||||
import i18n from '@/lang';
|
||||
@@ -11,8 +11,6 @@ import { changeToLocal } from '@/utils/node';
|
||||
import { getCookie } from '@/utils/auth';
|
||||
import { handleAuthResponseCode } from '@/utils/auth-response';
|
||||
|
||||
const getGlobalStore = () => GlobalStore();
|
||||
|
||||
const config = {
|
||||
baseURL: import.meta.env.VITE_API_URL as string,
|
||||
timeout: ResultEnum.TIMEOUT as number,
|
||||
@@ -30,14 +28,13 @@ class RequestHttp {
|
||||
this.service = axios.create(config);
|
||||
this.service.interceptors.request.use(
|
||||
(config: AxiosRequestConfig) => {
|
||||
const globalStore = getGlobalStore();
|
||||
let language = globalStore.language;
|
||||
const { csrfToken: csrfTokenRef, currentNode, entrance, language } = useGlobalStore();
|
||||
config.headers = {
|
||||
'Accept-Language': language,
|
||||
'Accept-Language': language.value,
|
||||
...config.headers,
|
||||
};
|
||||
if (config.headers.CurrentNode == undefined) {
|
||||
config.headers.CurrentNode = encodeURIComponent(globalStore.currentNode);
|
||||
config.headers.CurrentNode = encodeURIComponent(currentNode.value);
|
||||
} else {
|
||||
config.headers.CurrentNode = encodeURIComponent(String(config.headers.CurrentNode));
|
||||
}
|
||||
@@ -47,8 +44,7 @@ class RequestHttp {
|
||||
config.url === '/core/auth/passkey/begin' ||
|
||||
config.url === '/core/auth/passkey/finish'
|
||||
) {
|
||||
let entrance = encodeBase64(globalStore.entrance);
|
||||
config.headers.EntranceCode = entrance;
|
||||
config.headers.EntranceCode = encodeBase64(entrance.value);
|
||||
}
|
||||
const method = (config.method || 'get').toUpperCase();
|
||||
const requiresToken = !['GET', 'HEAD', 'OPTIONS', 'TRACE'].includes(method);
|
||||
@@ -56,7 +52,7 @@ class RequestHttp {
|
||||
const csrfToken = getCookie('pcsrftoken');
|
||||
if (csrfToken) {
|
||||
config.headers['X-CSRF-Token'] = csrfToken;
|
||||
globalStore.csrfToken = csrfToken;
|
||||
csrfTokenRef.value = csrfToken;
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -70,7 +66,7 @@ class RequestHttp {
|
||||
|
||||
this.service.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
const globalStore = getGlobalStore();
|
||||
const { isEnterpriseLicensed, isLoading, isLogin, isProductPro, loadingText } = useGlobalStore();
|
||||
const { data } = response;
|
||||
const authResult = handleAuthResponseCode(data, { showRBACMessage: true });
|
||||
if (authResult.handled) {
|
||||
@@ -80,14 +76,14 @@ class RequestHttp {
|
||||
return Promise.reject(data);
|
||||
}
|
||||
if (data.code == ResultEnum.ERR_XPACK) {
|
||||
globalStore.isProductPro = false;
|
||||
isProductPro.value = false;
|
||||
window.location.reload();
|
||||
return Promise.reject(data);
|
||||
}
|
||||
if (data.code == ResultEnum.ERR_ENTERPRISE) {
|
||||
globalStore.isEnterpriseLicensed = false;
|
||||
isEnterpriseLicensed.value = false;
|
||||
const routeName = router.currentRoute.value.name;
|
||||
if (globalStore.isLogin && routeName !== 'EnterpriseLicenseRequired') {
|
||||
if (isLogin.value && routeName !== 'EnterpriseLicenseRequired') {
|
||||
router.push({ name: 'EnterpriseLicenseRequired' });
|
||||
}
|
||||
return Promise.reject(data);
|
||||
@@ -98,14 +94,12 @@ class RequestHttp {
|
||||
return;
|
||||
}
|
||||
if (data.code == ResultEnum.ERR_GLOBAL_LOADING) {
|
||||
globalStore.$patch({
|
||||
isLoading: true,
|
||||
loadingText: data.message,
|
||||
});
|
||||
isLoading.value = true;
|
||||
loadingText.value = data.message;
|
||||
return;
|
||||
} else {
|
||||
if (globalStore.isLoading) {
|
||||
globalStore.isLoading = false;
|
||||
if (isLoading.value) {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
if (data.code == ResultEnum.ERR_AUTH) {
|
||||
|
||||
@@ -4,8 +4,7 @@ import { encodeBase64Fields } from '@/utils/base64';
|
||||
import { ResPage } from '../interface';
|
||||
import { Backup } from '../interface/backup';
|
||||
import { TimeoutEnum } from '@/enums/http-enum';
|
||||
import { GlobalStore } from '@/store';
|
||||
const getGlobalStore = () => GlobalStore();
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
// backup-agent
|
||||
export const getLocalBackupDir = (node?: string) => {
|
||||
@@ -16,19 +15,19 @@ export const searchBackup = (params: Backup.SearchWithType) => {
|
||||
return http.post<ResPage<Backup.BackupInfo>>(`/backups/search`, params);
|
||||
};
|
||||
export const checkBackup = (params: Backup.BackupOperate) => {
|
||||
const globalStore = getGlobalStore();
|
||||
const { isProductPro } = useGlobalStore();
|
||||
let request = deepCopy(params) as Backup.BackupOperate;
|
||||
encodeBase64Fields(request, ['accessKey', 'credential']);
|
||||
if (!params.isPublic || !globalStore.isProductPro) {
|
||||
if (!params.isPublic || !isProductPro.value) {
|
||||
return http.postLocalNode<Backup.CheckResult>(`/backups/conn/check`, request);
|
||||
}
|
||||
return http.post<Backup.CheckResult>(`/backups/conn/check`, request);
|
||||
};
|
||||
export const listBucket = (params: Backup.ForBucket) => {
|
||||
const globalStore = getGlobalStore();
|
||||
const { isProductPro } = useGlobalStore();
|
||||
let request = deepCopy(params) as Backup.BackupOperate;
|
||||
encodeBase64Fields(request, ['accessKey', 'credential']);
|
||||
if (!params.isPublic || !globalStore.isProductPro) {
|
||||
if (!params.isPublic || !isProductPro.value) {
|
||||
return http.postLocalNode('/backups/buckets', request, TimeoutEnum.T_40S);
|
||||
}
|
||||
return http.post('/backups/buckets', request, TimeoutEnum.T_40S);
|
||||
|
||||
@@ -75,11 +75,10 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
const slots = useSlots();
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const slots = useSlots();
|
||||
|
||||
const { isMobile } = useGlobalStore();
|
||||
const { isMobile, openMenuTabs } = useGlobalStore();
|
||||
|
||||
defineOptions({ name: 'ComplexTable' });
|
||||
export interface DropdownProps {
|
||||
@@ -108,7 +107,7 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['search', 'update:selects', 'update:paginationConfig']);
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const tableRef = ref();
|
||||
const tableHeight = ref<number | string>('');
|
||||
const menuRef = ref<HTMLElement | null>(null);
|
||||
@@ -283,7 +282,7 @@ defineExpose({
|
||||
|
||||
function calcHeight() {
|
||||
let heightDiff = props.heightDiff ?? 320;
|
||||
let tabHeight = globalStore.openMenuTabs ? 48 : 0;
|
||||
let tabHeight = openMenuTabs.value ? 48 : 0;
|
||||
|
||||
if (props.height) {
|
||||
tableHeight.value = props.height - tabHeight;
|
||||
|
||||
@@ -59,9 +59,8 @@
|
||||
import { computed, useSlots, ref } from 'vue';
|
||||
defineOptions({ name: 'DrawerPro' });
|
||||
import i18n from '@/lang';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
const drawerContent = ref();
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { isFullScreen } = useGlobalStore();
|
||||
|
||||
const isFull = ref();
|
||||
|
||||
@@ -132,7 +131,7 @@ const handleBack = () => {
|
||||
props.back();
|
||||
} else {
|
||||
localOpenPage.value = false;
|
||||
globalStore.isFullScreen = false;
|
||||
isFullScreen.value = false;
|
||||
}
|
||||
};
|
||||
emit('beforeClose', done);
|
||||
@@ -141,14 +140,14 @@ const handleBack = () => {
|
||||
props.back();
|
||||
} else {
|
||||
localOpenPage.value = false;
|
||||
globalStore.isFullScreen = false;
|
||||
isFullScreen.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
localOpenPage.value = false;
|
||||
globalStore.isFullScreen = false;
|
||||
isFullScreen.value = false;
|
||||
emit('close');
|
||||
};
|
||||
|
||||
@@ -161,10 +160,10 @@ const beforeClose = (done: () => void) => {
|
||||
};
|
||||
|
||||
function toggleFullscreen() {
|
||||
globalStore.isFullScreen = !globalStore.isFullScreen;
|
||||
isFull.value = globalStore.isFullScreen;
|
||||
isFullScreen.value = !isFullScreen.value;
|
||||
isFull.value = isFullScreen.value;
|
||||
}
|
||||
const loadTooltip = () => {
|
||||
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
|
||||
return i18n.global.t('commons.button.' + (isFullScreen.value ? 'quitFullscreen' : 'fullscreen'));
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<el-dropdown-item v-bind="$attrs" :disabled="computedDisabled">
|
||||
<slot></slot>
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { hasManagePermissionAccess, type PermissionBindingValue } from '@/utils/permission';
|
||||
|
||||
defineOptions({
|
||||
name: 'FuDropdownItem',
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
permission: {
|
||||
type: [String, Array],
|
||||
default: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const permissionDisabled = ref(false);
|
||||
|
||||
const hasPermission = computed(() => hasManagePermissionAccess(props.permission as PermissionBindingValue));
|
||||
|
||||
const computedDisabled = computed(() => props.disabled || permissionDisabled.value || !hasPermission.value);
|
||||
|
||||
defineExpose({
|
||||
setPermissionDisabled: (disabled: boolean) => {
|
||||
permissionDisabled.value = disabled;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -42,6 +42,11 @@ const emit = defineEmits(['update:modelValue', 'input', 'blur', 'enter']);
|
||||
|
||||
const inputRef = ref();
|
||||
const isWrite = ref(false);
|
||||
const permissionDisabled = ref(false);
|
||||
|
||||
const effectiveWriteTrigger = computed(() => {
|
||||
return permissionDisabled.value ? 'disabled' : props.writeTrigger;
|
||||
});
|
||||
|
||||
const displayValue = computed(() => {
|
||||
return props.modelValue === '' || props.modelValue === undefined || props.modelValue === null
|
||||
@@ -61,13 +66,13 @@ const closeWrite = () => {
|
||||
};
|
||||
|
||||
const handleReadClick = () => {
|
||||
if (props.writeTrigger === 'onClick') {
|
||||
if (effectiveWriteTrigger.value === 'onClick') {
|
||||
openWrite();
|
||||
}
|
||||
};
|
||||
|
||||
const handleReadDblClick = () => {
|
||||
if (props.writeTrigger === 'onDblclick') {
|
||||
if (effectiveWriteTrigger.value === 'onDblclick') {
|
||||
openWrite();
|
||||
}
|
||||
};
|
||||
@@ -89,4 +94,15 @@ const handleEnter = (event: KeyboardEvent) => {
|
||||
emit('enter', event);
|
||||
closeWrite();
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
setPermissionDisabled: (disabled: boolean) => {
|
||||
permissionDisabled.value = disabled;
|
||||
if (disabled) {
|
||||
isWrite.value = false;
|
||||
}
|
||||
},
|
||||
write: openWrite,
|
||||
read: closeWrite,
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -39,6 +39,11 @@ const props = defineProps({
|
||||
const emit = defineEmits(['update:modelValue', 'change']);
|
||||
|
||||
const isWrite = ref(false);
|
||||
const permissionDisabled = ref(false);
|
||||
|
||||
const effectiveWriteTrigger = computed(() => {
|
||||
return permissionDisabled.value ? 'disabled' : props.writeTrigger;
|
||||
});
|
||||
|
||||
const displayValue = computed(() => {
|
||||
const value = props.modelValue !== '' && props.modelValue !== undefined ? props.modelValue : props.data;
|
||||
@@ -58,18 +63,24 @@ const closeWrite = (value?: any) => {
|
||||
};
|
||||
|
||||
const handleReadClick = () => {
|
||||
if (props.writeTrigger === 'onClick') {
|
||||
if (effectiveWriteTrigger.value === 'onClick') {
|
||||
openWrite();
|
||||
}
|
||||
};
|
||||
|
||||
const handleReadDblClick = () => {
|
||||
if (props.writeTrigger === 'onDblclick') {
|
||||
if (effectiveWriteTrigger.value === 'onDblclick') {
|
||||
openWrite();
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
setPermissionDisabled: (disabled: boolean) => {
|
||||
permissionDisabled.value = disabled;
|
||||
if (disabled) {
|
||||
isWrite.value = false;
|
||||
}
|
||||
},
|
||||
write: openWrite,
|
||||
read: closeWrite,
|
||||
});
|
||||
|
||||
@@ -50,6 +50,20 @@ const emit = defineEmits(['update:modelValue', 'input', 'blur', 'change']);
|
||||
|
||||
const selectRef = ref();
|
||||
const isWrite = ref(false);
|
||||
const permissionDisabled = ref(false);
|
||||
|
||||
defineExpose({
|
||||
setPermissionDisabled: (disabled: boolean) => {
|
||||
permissionDisabled.value = disabled;
|
||||
if (disabled) {
|
||||
isWrite.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const effectiveWriteTrigger = computed(() => {
|
||||
return permissionDisabled.value ? 'disabled' : props.writeTrigger;
|
||||
});
|
||||
|
||||
const displayValue = computed(() => {
|
||||
return props.modelValue === '' || props.modelValue === undefined || props.modelValue === null
|
||||
@@ -70,13 +84,13 @@ const closeWrite = () => {
|
||||
};
|
||||
|
||||
const handleReadClick = () => {
|
||||
if (props.writeTrigger === 'onClick') {
|
||||
if (effectiveWriteTrigger.value === 'onClick') {
|
||||
openWrite();
|
||||
}
|
||||
};
|
||||
|
||||
const handleReadDblClick = () => {
|
||||
if (props.writeTrigger === 'onDblclick') {
|
||||
if (effectiveWriteTrigger.value === 'onDblclick') {
|
||||
openWrite();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,6 +59,7 @@ import { computed, type PropType } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { resolveMaybeFn, type FuTableOperationButton } from './shared';
|
||||
import { hasManagePermissionAccess } from '@/utils/permission';
|
||||
|
||||
defineOptions({ name: 'FuTableOperations' });
|
||||
|
||||
@@ -204,7 +205,10 @@ const getMoreButtons = (row: any) => {
|
||||
};
|
||||
|
||||
const isButtonDisabled = (button: FuTableOperationButton, row: any) => {
|
||||
return Boolean(resolveMaybeFn(button.disabled ?? false, row));
|
||||
const permissionDisabled =
|
||||
button.permission !== undefined &&
|
||||
!hasManagePermissionAccess(button.permission === true ? undefined : button.permission);
|
||||
return permissionDisabled || Boolean(resolveMaybeFn(button.disabled ?? false, row));
|
||||
};
|
||||
|
||||
const handleButtonClick = (button: FuTableOperationButton, row: any) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { type App } from 'vue';
|
||||
|
||||
import FuInputRwSwitch from './FuInputRwSwitch.vue';
|
||||
import FuDropdownItem from './FuDropdownItem.vue';
|
||||
import FuReadWriteSwitch from './FuReadWriteSwitch.vue';
|
||||
import FuSelectRwSwitch from './FuSelectRwSwitch.vue';
|
||||
import FuStep from './FuStep';
|
||||
@@ -14,6 +15,7 @@ const components = [
|
||||
FuTable,
|
||||
FuTableOperations,
|
||||
FuTablePagination,
|
||||
FuDropdownItem,
|
||||
FuInputRwSwitch,
|
||||
FuReadWriteSwitch,
|
||||
FuSelectRwSwitch,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Comment, Fragment, Text, type VNode } from 'vue';
|
||||
import type { PermissionBindingValue } from '@/utils/permission';
|
||||
|
||||
export interface FuTableColumnConfig {
|
||||
key: string;
|
||||
@@ -12,6 +13,7 @@ export interface FuTableOperationButton {
|
||||
label?: string | number;
|
||||
click?: (row: any) => void;
|
||||
disabled?: boolean | ((row: any) => boolean);
|
||||
permission?: true | PermissionBindingValue;
|
||||
show?: boolean | ((row: any) => boolean);
|
||||
type?: string;
|
||||
icon?: any;
|
||||
|
||||
@@ -61,10 +61,10 @@ import { ref } from 'vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { uploadLicense, uploadEnterpriseLicense } from '@/api/modules/setting';
|
||||
import DockerProxy from '@/components/docker-proxy/index.vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { UploadFile, UploadFiles, UploadInstance, UploadProps, UploadRawFile, genFileId } from 'element-plus';
|
||||
import { getXpackSettingForTheme, loadMasterProductProFromDB, loadProductProFromDB } from '@/utils/xpack';
|
||||
const globalStore = GlobalStore();
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { isIntl, isEnterprise, currentNode, isProductPro, isMasterProductPro, isEnterpriseLicensed } = useGlobalStore();
|
||||
|
||||
const em = defineEmits(['search']);
|
||||
|
||||
@@ -111,9 +111,9 @@ const handleExceed: UploadProps['onExceed'] = (files) => {
|
||||
uploadRef.value!.handleStart(file);
|
||||
};
|
||||
|
||||
const toEdition = () => {
|
||||
if (!globalStore.isIntl) {
|
||||
window.open('https://1panel.cn/versions.html' + '', '_blank', 'noopener,noreferrer');
|
||||
const toLxware = () => {
|
||||
if (!isIntl.value) {
|
||||
window.open('https://www.lxware.cn/1panel' + '', '_blank', 'noopener,noreferrer');
|
||||
} else {
|
||||
window.open('https://1panel.pro/pricing' + '', '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
@@ -126,7 +126,7 @@ const submit = async () => {
|
||||
const file = uploaderFiles.value[0];
|
||||
const formData = new FormData();
|
||||
formData.append('file', file.raw);
|
||||
if (globalStore.isEnterprise) {
|
||||
if (isEnterprise.value) {
|
||||
loading.value = true;
|
||||
await uploadEnterpriseLicense(formData)
|
||||
.then(async () => {
|
||||
@@ -143,7 +143,7 @@ const submit = async () => {
|
||||
formData.append('oldLicenseName', oldLicense.value);
|
||||
}
|
||||
if (!isImport.value) {
|
||||
formData.append('currentNode', globalStore.currentNode);
|
||||
formData.append('currentNode', currentNode.value);
|
||||
formData.append('withDockerRestart', withDockerRestart.value);
|
||||
}
|
||||
formData.append('isForce', isForce.value);
|
||||
@@ -166,10 +166,10 @@ const handleAfterSubmit = () => {
|
||||
open.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
if (!isImport.value) {
|
||||
if (!globalStore.isEnterprise) globalStore.isProductPro = true;
|
||||
globalStore.isMasterProductPro = true;
|
||||
if (!isEnterprise.value) isProductPro.value = true;
|
||||
isMasterProductPro.value = true;
|
||||
} else {
|
||||
globalStore.isEnterpriseLicensed = true;
|
||||
isEnterpriseLicensed.value = true;
|
||||
}
|
||||
if (!withoutReload.value) {
|
||||
loadMasterProductProFromDB();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-model="open"
|
||||
:header="resource"
|
||||
@close="handleClose"
|
||||
:size="globalStore.isFullScreen ? 'full' : '60%'"
|
||||
:size="isFullScreen ? 'full' : '60%'"
|
||||
:resource="container"
|
||||
>
|
||||
<template #extra v-if="!isMobile">
|
||||
@@ -26,16 +26,14 @@
|
||||
<script lang="ts" setup>
|
||||
import i18n from '@/lang';
|
||||
import { onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import screenfull from 'screenfull';
|
||||
import ContainerLog from '@/components/log/container/index.vue';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const { isMobile } = useGlobalStore();
|
||||
const { isMobile, isFullScreen } = useGlobalStore();
|
||||
const open = ref(false);
|
||||
const resource = ref('');
|
||||
const container = ref('');
|
||||
const globalStore = GlobalStore();
|
||||
const logVisible = ref(false);
|
||||
const compose = ref('');
|
||||
const highlightDiff = ref(150);
|
||||
@@ -57,14 +55,14 @@ const defaultProps = defineProps({
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
globalStore.isFullScreen = false;
|
||||
isFullScreen.value = false;
|
||||
};
|
||||
|
||||
function toggleFullscreen() {
|
||||
globalStore.isFullScreen = !globalStore.isFullScreen;
|
||||
isFullScreen.value = !isFullScreen.value;
|
||||
}
|
||||
const loadTooltip = () => {
|
||||
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
|
||||
return i18n.global.t('commons.button.' + (isFullScreen.value ? 'quitFullscreen' : 'fullscreen'));
|
||||
};
|
||||
|
||||
watch(logVisible, (val) => {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
:header="$t('commons.button.log')"
|
||||
@close="handleClose"
|
||||
:resource="logSearch.container"
|
||||
:size="globalStore.isFullScreen ? 'full' : '60%'"
|
||||
:size="isFullScreen ? 'full' : '60%'"
|
||||
>
|
||||
<template #extra v-if="!isMobile">
|
||||
<el-tooltip :content="loadTooltip()" placement="top">
|
||||
@@ -26,14 +26,12 @@
|
||||
import i18n from '@/lang';
|
||||
import { onBeforeUnmount, reactive, ref, watch } from 'vue';
|
||||
import screenfull from 'screenfull';
|
||||
import { GlobalStore } from '@/store';
|
||||
import ContainerLog from '@/components/log/container/index.vue';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const { isMobile } = useGlobalStore();
|
||||
const { isMobile, isFullScreen } = useGlobalStore();
|
||||
|
||||
const logVisible = ref(false);
|
||||
const globalStore = GlobalStore();
|
||||
const logSearch = reactive({
|
||||
isWatch: true,
|
||||
container: '',
|
||||
@@ -50,16 +48,16 @@ defineProps({
|
||||
});
|
||||
|
||||
function toggleFullscreen() {
|
||||
globalStore.isFullScreen = !globalStore.isFullScreen;
|
||||
isFullScreen.value = !isFullScreen.value;
|
||||
}
|
||||
|
||||
const loadTooltip = () => {
|
||||
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
|
||||
return i18n.global.t('commons.button.' + (isFullScreen.value ? 'quitFullscreen' : 'fullscreen'));
|
||||
};
|
||||
|
||||
const handleClose = async () => {
|
||||
logVisible.value = false;
|
||||
globalStore.isFullScreen = false;
|
||||
isFullScreen.value = false;
|
||||
};
|
||||
|
||||
watch(logVisible, (val) => {
|
||||
@@ -84,7 +82,7 @@ const acceptParams = (props: DialogProps): void => {
|
||||
|
||||
if (!isMobile.value) {
|
||||
screenfull.on('change', () => {
|
||||
globalStore.isFullScreen = screenfull.isFullscreen;
|
||||
isFullScreen.value = screenfull.isFullscreen;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -79,9 +79,9 @@ import { dateFormatForName } from '@/utils/date';
|
||||
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { checkStreamAuth } from '@/utils/stream-auth';
|
||||
const globalStore = GlobalStore();
|
||||
const { currentNode: globalCurrentNode } = useGlobalStore();
|
||||
|
||||
const em = defineEmits(['update:loading']);
|
||||
|
||||
@@ -239,7 +239,7 @@ const searchLogs = async () => {
|
||||
stopListening();
|
||||
clearTerminal();
|
||||
|
||||
let currentNode = globalStore.currentNode;
|
||||
let currentNode = globalCurrentNode.value;
|
||||
if (props.node && props.node !== '') {
|
||||
currentNode = props.node;
|
||||
}
|
||||
@@ -311,7 +311,7 @@ const onClean = async () => {
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
let currentNode = globalStore.currentNode;
|
||||
let currentNode = globalCurrentNode.value;
|
||||
if (props.node && props.node !== '') {
|
||||
currentNode = props.node;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-model="open"
|
||||
:header="$t('commons.button.log')"
|
||||
@close="handleClose"
|
||||
:size="globalStore.isFullScreen ? 'full' : 'large'"
|
||||
:size="isFullScreen ? 'full' : 'large'"
|
||||
>
|
||||
<template #extra v-if="!isMobile">
|
||||
<el-tooltip :content="loadTooltip()" placement="top">
|
||||
@@ -18,14 +18,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import LogFile from '@/components/log/file/index.vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import i18n from '@/lang';
|
||||
import screenfull from 'screenfull';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const { isMobile } = useGlobalStore();
|
||||
const { isMobile, isFullScreen } = useGlobalStore();
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
interface LogProps {
|
||||
id: number;
|
||||
type: string;
|
||||
@@ -50,15 +48,15 @@ const em = defineEmits(['close']);
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
globalStore.isFullScreen = false;
|
||||
isFullScreen.value = false;
|
||||
em('close', false);
|
||||
};
|
||||
|
||||
function toggleFullscreen() {
|
||||
globalStore.isFullScreen = !globalStore.isFullScreen;
|
||||
isFullScreen.value = !isFullScreen.value;
|
||||
}
|
||||
const loadTooltip = () => {
|
||||
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
|
||||
return i18n.global.t('commons.button.' + (isFullScreen.value ? 'quitFullscreen' : 'fullscreen'));
|
||||
};
|
||||
|
||||
watch(open, (val) => {
|
||||
|
||||
@@ -53,10 +53,10 @@ import { nextTick, onMounted, onUnmounted, reactive, ref, computed } from 'vue';
|
||||
import { downloadFile } from '@/utils/file';
|
||||
import { readByLine } from '@/api/modules/files';
|
||||
import { readTaskLogByLine } from '@/api/modules/log';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import bus from '@/global/bus';
|
||||
import Highlight from '@/components/log/custom-highlight/index.vue';
|
||||
const globalStore = GlobalStore();
|
||||
const { currentNode } = useGlobalStore();
|
||||
|
||||
interface LogProps {
|
||||
id?: number;
|
||||
@@ -233,7 +233,7 @@ const changeLoading = () => {
|
||||
|
||||
const onDownload = async () => {
|
||||
changeLoading();
|
||||
downloadFile(logPath.value, props.config.operateNode || globalStore.currentNode);
|
||||
downloadFile(logPath.value, props.config.operateNode || currentNode.value);
|
||||
changeLoading();
|
||||
};
|
||||
|
||||
@@ -269,7 +269,7 @@ const getContent = async (pre: boolean) => {
|
||||
|
||||
let res;
|
||||
try {
|
||||
const operateNode = props.config.operateNode || globalStore.currentNode;
|
||||
const operateNode = props.config.operateNode || currentNode.value;
|
||||
if (readReq.type === 'task') {
|
||||
res = await readTaskLogByLine(readReq, operateNode);
|
||||
} else {
|
||||
|
||||
@@ -21,8 +21,8 @@ import i18n from '@/lang';
|
||||
import { MsgError, MsgWarning } from '@/utils/message';
|
||||
import { jumpToPath } from '@/utils/router';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { currentNodeAddr, isMaster } = useGlobalStore();
|
||||
const router = useRouter();
|
||||
|
||||
const open = ref();
|
||||
@@ -44,8 +44,8 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||
let protocol = params.protocol === 'https' ? 'https' : 'http';
|
||||
const res = await getAgentSettingInfo();
|
||||
if (!res.data.systemIP) {
|
||||
if (!globalStore.isMaster || globalStore.currentNodeAddr != '127.0.0.1') {
|
||||
res.data.systemIP = globalStore.currentNodeAddr;
|
||||
if (!isMaster.value || currentNodeAddr.value != '127.0.0.1') {
|
||||
res.data.systemIP = currentNodeAddr.value;
|
||||
} else {
|
||||
open.value = true;
|
||||
return;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-tag>
|
||||
<el-button size="small" v-else :type="getType(statusItem)" plain round>
|
||||
<el-button size="small" v-else :type="getType(statusItem)" plain round :disabled="isDisabled">
|
||||
<span v-if="statusItem != ''">{{ $t('commons.status.' + statusItem) }}</span>
|
||||
<el-icon v-if="loadingIcon(statusItem)" class="is-loading">
|
||||
<Loading />
|
||||
@@ -33,12 +33,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
status: String,
|
||||
msg: String,
|
||||
hasIcon: Boolean,
|
||||
disabled: Boolean,
|
||||
operate: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -46,10 +47,22 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const permissionDisabled = ref(false);
|
||||
|
||||
defineExpose({
|
||||
setPermissionDisabled: (disabled: boolean) => {
|
||||
permissionDisabled.value = disabled;
|
||||
},
|
||||
});
|
||||
|
||||
const statusItem = computed(() => {
|
||||
return props.status?.toLowerCase() || '';
|
||||
});
|
||||
|
||||
const isDisabled = computed(() => {
|
||||
return !!props.disabled || permissionDisabled.value;
|
||||
});
|
||||
|
||||
const getType = (status: string) => {
|
||||
if (status.includes('error') || status.includes('err')) {
|
||||
return 'danger';
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</span>
|
||||
<div class="flex flex-wrap items-center">
|
||||
<el-link underline="never" type="primary" @click="toLxware">
|
||||
<span v-if="isEnterprise">
|
||||
<span v-if="isEE">
|
||||
{{ $t('license.ee') }}
|
||||
</span>
|
||||
<span v-else-if="isMasterPro">
|
||||
@@ -45,9 +45,9 @@
|
||||
</el-link>
|
||||
<el-badge
|
||||
is-dot
|
||||
v-if="globalStore.isAdmin && !globalStore.isOffline"
|
||||
v-if="isAdmin && !isOffline"
|
||||
class="-mt-0.5"
|
||||
:hidden="version === 'Waiting' || !globalStore.hasNewVersion"
|
||||
:hidden="version === 'Waiting' || !hasNewVersion"
|
||||
>
|
||||
<el-link class="ml-2" underline="never" type="primary" @click="onLoadUpgradeInfo">
|
||||
{{ $t('commons.button.update') }}
|
||||
@@ -70,19 +70,11 @@ import Releases from '@/components/system-upgrade/releases/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const { docsUrl, isOffline, isFxplay } = storeToRefs(globalStore);
|
||||
const { docsUrl, isOffline, isFxplay, isMasterPro, isEE, isIntl, isAdmin, hasNewVersion } = useGlobalStore();
|
||||
const upgradeRef = ref();
|
||||
const releasesRef = ref();
|
||||
const isMasterPro = computed(() => {
|
||||
return globalStore.isMasterPro();
|
||||
});
|
||||
const isEnterprise = computed(() => {
|
||||
return globalStore.isEE();
|
||||
});
|
||||
|
||||
const version = ref<string>('');
|
||||
const loading = ref(false);
|
||||
@@ -112,7 +104,7 @@ const toLxware = () => {
|
||||
to1Panel();
|
||||
return;
|
||||
}
|
||||
if (!globalStore.isIntl) {
|
||||
if (!isIntl.value) {
|
||||
window.open('https://www.lxware.cn/1panel' + '', '_blank', 'noopener,noreferrer');
|
||||
} else {
|
||||
window.open('https://1panel.pro/pricing' + '', '_blank', 'noopener,noreferrer');
|
||||
@@ -120,7 +112,7 @@ const toLxware = () => {
|
||||
};
|
||||
|
||||
const to1Panel = () => {
|
||||
let url = globalStore.isIntl ? 'https://1panel.pro' : 'https://1panel.cn';
|
||||
let url = isIntl.value ? 'https://1panel.pro' : 'https://1panel.cn';
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
@@ -137,9 +129,7 @@ const toEdition = () => {
|
||||
};
|
||||
|
||||
const toForum = () => {
|
||||
let url = globalStore.isIntl
|
||||
? 'https://github.com/1Panel-dev/1Panel/discussions'
|
||||
: 'https://bbs.fit2cloud.com/c/1p/7';
|
||||
let url = isIntl.value ? 'https://github.com/1Panel-dev/1Panel/discussions' : 'https://bbs.fit2cloud.com/c/1p/7';
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ import { loadReleaseNotes, upgrade } from '@/api/modules/setting';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { ref } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const { isLoading, isOnRestart } = useGlobalStore();
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const upgradeInfo = ref();
|
||||
@@ -80,8 +80,8 @@ const onUpgrade = async () => {
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
await upgrade(upgradeVersion.value);
|
||||
globalStore.isLoading = true;
|
||||
globalStore.isOnRestart = true;
|
||||
isLoading.value = true;
|
||||
isOnRestart.value = true;
|
||||
drawerVisible.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
|
||||
@@ -55,8 +55,8 @@ import { searchTasks } from '@/api/modules/log';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { Log } from '@/api/interface/log';
|
||||
import bus from '@/global/bus';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { currentNode } = useGlobalStore();
|
||||
|
||||
const open = ref(false);
|
||||
const handleClose = () => {
|
||||
@@ -101,7 +101,7 @@ const openTaskLog = (row: Log.Task) => {
|
||||
};
|
||||
|
||||
const acceptParams = () => {
|
||||
targeNode.value = globalStore.currentNode;
|
||||
targeNode.value = currentNode.value;
|
||||
search();
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
@@ -22,10 +22,11 @@ import { Terminal } from '@xterm/xterm';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { decodeBase64, encodeBase64 } from '@/utils/base64';
|
||||
import { GlobalStore, TerminalStore } from '@/store';
|
||||
import { TerminalStore } from '@/store';
|
||||
import { MsgError } from '@/utils/message';
|
||||
import { checkStreamAuth } from '@/utils/stream-auth';
|
||||
const globalStore = GlobalStore();
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { currentNode } = useGlobalStore();
|
||||
|
||||
const terminalElement = ref<HTMLDivElement | null>(null);
|
||||
const fitAddon = new FitAddon();
|
||||
@@ -252,7 +253,7 @@ const initWebSocket = async (endpoint_: string, args: string = '') => {
|
||||
const protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
|
||||
const host = href.split('//')[1].split('/')[0];
|
||||
const endpoint = endpoint_.replace(/^\/+/, '');
|
||||
let node = args.indexOf('id=') !== -1 ? 'local' : globalStore.currentNode;
|
||||
let node = args.indexOf('id=') !== -1 ? 'local' : currentNode.value;
|
||||
let conn = `${protocol}://${host}/${endpoint}?cols=${term.value.cols}&rows=${term.value.rows}&${args}&operateNode=${node}`;
|
||||
if (args.indexOf('operateNode=') !== -1) {
|
||||
conn = `${protocol}://${host}/${endpoint}?cols=${term.value.cols}&rows=${term.value.rows}&${args}`;
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, nextTick, watch, onBeforeUnmount, ref } from 'vue';
|
||||
import echarts from '@/utils/echarts';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { computeSizeFromKBs, computeSizeFromKB, computeSizeFromMB } from '@/utils/size';
|
||||
import i18n from '@/lang';
|
||||
const globalStore = GlobalStore();
|
||||
const { themeConfig } = useGlobalStore();
|
||||
const isDarkTheme = ref(false);
|
||||
let mediaQuery: MediaQueryList;
|
||||
const props = defineProps({
|
||||
@@ -99,10 +99,10 @@ const seriesStyle = [
|
||||
];
|
||||
|
||||
function initChart() {
|
||||
if (globalStore.themeConfig.theme === 'auto') {
|
||||
if (themeConfig.value.theme === 'auto') {
|
||||
isDarkTheme.value = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
} else {
|
||||
isDarkTheme.value = globalStore.themeConfig.theme === 'dark';
|
||||
isDarkTheme.value = themeConfig.value.theme === 'dark';
|
||||
}
|
||||
let itemChart = echarts?.getInstanceByDom(document.getElementById(props.id) as HTMLElement);
|
||||
const optionItem = itemChart?.getOption();
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, nextTick, watch, onBeforeUnmount, ref } from 'vue';
|
||||
import echarts from '@/utils/echarts';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { themeConfig } = useGlobalStore();
|
||||
const isDarkTheme = ref(false);
|
||||
let mediaQuery: MediaQueryList;
|
||||
|
||||
@@ -45,10 +45,10 @@ function getThemeColors() {
|
||||
}
|
||||
|
||||
function initChart() {
|
||||
if (globalStore.themeConfig.theme === 'auto') {
|
||||
if (themeConfig.value.theme === 'auto') {
|
||||
isDarkTheme.value = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
} else {
|
||||
isDarkTheme.value = globalStore.themeConfig.theme === 'dark';
|
||||
isDarkTheme.value = themeConfig.value.theme === 'dark';
|
||||
}
|
||||
let myChart = echarts?.getInstanceByDom(document.getElementById(props.id) as HTMLElement);
|
||||
if (myChart === null || myChart === undefined) {
|
||||
|
||||
@@ -154,7 +154,7 @@ import { computed, reactive, ref } from 'vue';
|
||||
import type { FormInstance, FormItemRule, FormRules } from 'element-plus';
|
||||
import { Base64 } from 'js-base64';
|
||||
import i18n from '@/lang';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { getSSHInfo, searchCert } from '@/api/modules/host';
|
||||
import { loadLocalConn } from '@/api/modules/terminal';
|
||||
import { Host } from '@/api/interface/host';
|
||||
@@ -162,7 +162,7 @@ import { Rules } from '@/global/form-rules';
|
||||
import { copyText } from '@/utils/clipboard';
|
||||
import { MsgError } from '@/utils/message';
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const { currentNode, currentNodeAddr } = useGlobalStore();
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
const showScriptPreview = ref(false);
|
||||
@@ -190,7 +190,7 @@ const defaultForm = () => ({
|
||||
|
||||
const addForm = reactive(defaultForm());
|
||||
|
||||
const getStorageKey = () => `${STORAGE_KEY}:${globalStore.currentNode}:${globalStore.currentNodeAddr || 'local'}`;
|
||||
const getStorageKey = () => `${STORAGE_KEY}:${currentNode.value}:${currentNodeAddr.value || 'local'}`;
|
||||
|
||||
const isKeyMode = computed(() => addForm.authMode === 'key');
|
||||
const selectedCert = computed(() => certOptions.value.find((item) => String(item.id) === String(addForm.certID)));
|
||||
@@ -376,11 +376,11 @@ const restoreDraft = () => {
|
||||
};
|
||||
|
||||
const loadConnectionInfo = async () => {
|
||||
if (globalStore.currentNode === 'local') {
|
||||
if (currentNode.value === 'local') {
|
||||
try {
|
||||
const res = await loadLocalConn();
|
||||
if (res.data) {
|
||||
addForm.host = res.data.addr || globalStore.currentNodeAddr || '127.0.0.1';
|
||||
addForm.host = res.data.addr || currentNodeAddr.value || '127.0.0.1';
|
||||
addForm.port = Number(res.data.port) || 22;
|
||||
addForm.username = res.data.user || 'root';
|
||||
return;
|
||||
@@ -396,7 +396,7 @@ const loadConnectionInfo = async () => {
|
||||
}
|
||||
} catch {}
|
||||
|
||||
addForm.host = globalStore.currentNodeAddr || addForm.host || '127.0.0.1';
|
||||
addForm.host = currentNodeAddr.value || addForm.host || '127.0.0.1';
|
||||
};
|
||||
|
||||
const loadCertOptions = async () => {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { hasManagePermissionAccess, hasPermissionAccess, toManagePermission } from '@/utils/permission';
|
||||
|
||||
const getRoutePermission = (route: ReturnType<typeof useRoute>) => {
|
||||
const metaPermission = route.meta?.permission;
|
||||
if (typeof metaPermission === 'string' && metaPermission) {
|
||||
return metaPermission;
|
||||
}
|
||||
|
||||
for (const record of [...route.matched].reverse()) {
|
||||
const permission = record.meta?.permission;
|
||||
if (typeof permission === 'string' && permission) {
|
||||
return permission;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
export const useMenuManagePermission = (permission?: string) => {
|
||||
const route = useRoute();
|
||||
const { isAdmin, isNodeAdmin } = useGlobalStore();
|
||||
|
||||
const sourcePermission = computed(() => {
|
||||
if (permission) {
|
||||
return permission;
|
||||
}
|
||||
return getRoutePermission(route);
|
||||
});
|
||||
const managePermission = computed(() => {
|
||||
return toManagePermission(sourcePermission.value);
|
||||
});
|
||||
const hasAdminManagePermission = computed(() => isAdmin.value || isNodeAdmin.value);
|
||||
const hasPermission = computed(() => {
|
||||
return hasPermissionAccess(sourcePermission.value || []);
|
||||
});
|
||||
const hasManagePermission = computed(() => {
|
||||
return hasManagePermissionAccess(managePermission.value || []);
|
||||
});
|
||||
|
||||
return {
|
||||
managePermission,
|
||||
hasAdminManagePermission,
|
||||
hasPermission,
|
||||
hasManagePermission,
|
||||
};
|
||||
};
|
||||
|
||||
export const useCan = (permission?: string) => {
|
||||
return useMenuManagePermission(permission).hasPermission;
|
||||
};
|
||||
@@ -1,8 +1,10 @@
|
||||
import { App, Directive } from 'vue';
|
||||
import integerInput from './modules/integer';
|
||||
import permission from './modules/permission';
|
||||
|
||||
const directivesList: { [key: string]: Directive } = {
|
||||
'integer-input': integerInput,
|
||||
permission,
|
||||
};
|
||||
|
||||
const directives = {
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import type { Directive, DirectiveBinding, VNode } from 'vue';
|
||||
import {
|
||||
hasManagePermissionAccess,
|
||||
hasPermissionAccess,
|
||||
type PermissionBindingValue,
|
||||
type PermissionMode,
|
||||
} from '@/utils/permission';
|
||||
|
||||
type PermissionControlledComponent = {
|
||||
setPermissionDisabled?: (disabled: boolean) => void;
|
||||
};
|
||||
|
||||
const PERMISSION_DISABLED_ATTR = 'data-permission-disabled';
|
||||
const PERMISSION_POINTER_EVENTS_ATTR = 'data-permission-pointer-events';
|
||||
const PERMISSION_NATIVE_DISABLED_ATTR = 'data-permission-native-disabled';
|
||||
const PERMISSION_TABINDEX_ATTR = 'data-permission-tabindex';
|
||||
|
||||
const getDisableTargets = (el: HTMLElement) => {
|
||||
const targets = [el, ...Array.from(el.querySelectorAll<HTMLElement>('button, input, select, textarea'))];
|
||||
return Array.from(new Set(targets));
|
||||
};
|
||||
|
||||
const disableNativeControls = (el: HTMLElement) => {
|
||||
for (const target of getDisableTargets(el)) {
|
||||
if (
|
||||
target instanceof HTMLButtonElement ||
|
||||
target instanceof HTMLInputElement ||
|
||||
target instanceof HTMLSelectElement ||
|
||||
target instanceof HTMLTextAreaElement
|
||||
) {
|
||||
if (!target.hasAttribute(PERMISSION_NATIVE_DISABLED_ATTR)) {
|
||||
target.setAttribute(PERMISSION_NATIVE_DISABLED_ATTR, target.disabled ? 'true' : 'false');
|
||||
}
|
||||
target.disabled = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!target.hasAttribute(PERMISSION_TABINDEX_ATTR)) {
|
||||
const tabindex = target.getAttribute('tabindex');
|
||||
target.setAttribute(PERMISSION_TABINDEX_ATTR, tabindex ?? '');
|
||||
}
|
||||
target.setAttribute('tabindex', '-1');
|
||||
}
|
||||
};
|
||||
|
||||
const enableNativeControls = (el: HTMLElement) => {
|
||||
for (const target of getDisableTargets(el)) {
|
||||
if (
|
||||
target instanceof HTMLButtonElement ||
|
||||
target instanceof HTMLInputElement ||
|
||||
target instanceof HTMLSelectElement ||
|
||||
target instanceof HTMLTextAreaElement
|
||||
) {
|
||||
const previousDisabled = target.getAttribute(PERMISSION_NATIVE_DISABLED_ATTR);
|
||||
if (previousDisabled !== null) {
|
||||
target.disabled = previousDisabled === 'true';
|
||||
target.removeAttribute(PERMISSION_NATIVE_DISABLED_ATTR);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const previousTabindex = target.getAttribute(PERMISSION_TABINDEX_ATTR);
|
||||
if (previousTabindex !== null) {
|
||||
if (previousTabindex === '') {
|
||||
target.removeAttribute('tabindex');
|
||||
} else {
|
||||
target.setAttribute('tabindex', previousTabindex);
|
||||
}
|
||||
target.removeAttribute(PERMISSION_TABINDEX_ATTR);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const disableElement = (el: HTMLElement) => {
|
||||
if (!el.hasAttribute(PERMISSION_POINTER_EVENTS_ATTR)) {
|
||||
el.setAttribute(PERMISSION_POINTER_EVENTS_ATTR, el.style.pointerEvents || '');
|
||||
}
|
||||
el.style.pointerEvents = 'none';
|
||||
el.setAttribute('aria-disabled', 'true');
|
||||
el.setAttribute(PERMISSION_DISABLED_ATTR, 'true');
|
||||
el.classList.add('is-disabled');
|
||||
disableNativeControls(el);
|
||||
};
|
||||
|
||||
const enableElement = (el: HTMLElement) => {
|
||||
if (!el.hasAttribute(PERMISSION_DISABLED_ATTR)) {
|
||||
return;
|
||||
}
|
||||
el.style.pointerEvents = el.getAttribute(PERMISSION_POINTER_EVENTS_ATTR) || '';
|
||||
el.removeAttribute('aria-disabled');
|
||||
el.removeAttribute(PERMISSION_DISABLED_ATTR);
|
||||
el.removeAttribute(PERMISSION_POINTER_EVENTS_ATTR);
|
||||
el.classList.remove('is-disabled');
|
||||
enableNativeControls(el);
|
||||
};
|
||||
|
||||
const getComponentPermissionController = (vnode: VNode): PermissionControlledComponent | undefined => {
|
||||
return vnode.component?.exposed as PermissionControlledComponent | undefined;
|
||||
};
|
||||
|
||||
const getPermissionMode = (binding: DirectiveBinding<PermissionBindingValue>): PermissionMode => {
|
||||
return binding.arg === 'view' ? 'view' : 'manage';
|
||||
};
|
||||
|
||||
const applyPermission = (el: HTMLElement, binding: DirectiveBinding<PermissionBindingValue>, vnode: VNode) => {
|
||||
const disabled =
|
||||
getPermissionMode(binding) === 'view'
|
||||
? !hasPermissionAccess(binding.value)
|
||||
: !hasManagePermissionAccess(binding.value);
|
||||
const controller = getComponentPermissionController(vnode);
|
||||
|
||||
if (controller?.setPermissionDisabled) {
|
||||
enableElement(el);
|
||||
controller.setPermissionDisabled(disabled);
|
||||
return;
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
disableElement(el);
|
||||
return;
|
||||
}
|
||||
enableElement(el);
|
||||
};
|
||||
|
||||
const permissionDirective: Directive<HTMLElement, PermissionBindingValue> = {
|
||||
mounted(el, binding, vnode) {
|
||||
applyPermission(el, binding, vnode);
|
||||
},
|
||||
updated(el, binding, vnode) {
|
||||
applyPermission(el, binding, vnode);
|
||||
},
|
||||
};
|
||||
|
||||
export default permissionDirective;
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { EditionFrontendProvider } from '../provider';
|
||||
|
||||
const communityProvider: EditionFrontendProvider = {
|
||||
name: 'community',
|
||||
routes: [],
|
||||
loadStyles: async () => {},
|
||||
};
|
||||
|
||||
export default communityProvider;
|
||||
@@ -1,17 +0,0 @@
|
||||
import communityProvider from './community';
|
||||
import type { EditionFrontendProvider } from './provider';
|
||||
import proProvider from '@xpack-pro/edition';
|
||||
import eeProvider from '@enterprise/edition';
|
||||
|
||||
const edition = (import.meta.env.VITE_FRONTEND_EDITION || 'community').toLowerCase();
|
||||
|
||||
export function loadEditionProvider(): EditionFrontendProvider {
|
||||
switch (edition) {
|
||||
case 'pro':
|
||||
return proProvider;
|
||||
case 'ee':
|
||||
return eeProvider;
|
||||
default:
|
||||
return communityProvider;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
export interface EditionFrontendProvider {
|
||||
name: string;
|
||||
routes: RouteRecordRaw[];
|
||||
loadStyles?: () => Promise<void>;
|
||||
}
|
||||
@@ -1,30 +1,30 @@
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { getXpackSetting } from '@/utils/xpack';
|
||||
|
||||
export const useLogo = async () => {
|
||||
const globalStore = GlobalStore();
|
||||
const { themeConfig, watermark, watermarkShow } = useGlobalStore();
|
||||
const res = await getXpackSetting();
|
||||
if (res) {
|
||||
localStorage.setItem('1p-favicon', res.data.logo);
|
||||
globalStore.themeConfig.title = res.data.title;
|
||||
globalStore.themeConfig.logo = res.data.logo;
|
||||
globalStore.themeConfig.logoWithText = res.data.logoWithText;
|
||||
globalStore.themeConfig.loginImage = res.data?.loginImage;
|
||||
globalStore.themeConfig.loginBgType = res.data?.loginBgType;
|
||||
globalStore.themeConfig.loginBackground = res.data?.loginBackground;
|
||||
globalStore.themeConfig.loginBtnLinkColor = res.data?.loginBtnLinkColor;
|
||||
globalStore.themeConfig.favicon = res.data.favicon;
|
||||
globalStore.watermarkShow = res.data.watermarkShow === 'Enable';
|
||||
themeConfig.value.title = res.data.title;
|
||||
themeConfig.value.logo = res.data.logo;
|
||||
themeConfig.value.logoWithText = res.data.logoWithText;
|
||||
themeConfig.value.loginImage = res.data?.loginImage;
|
||||
themeConfig.value.loginBgType = res.data?.loginBgType;
|
||||
themeConfig.value.loginBackground = res.data?.loginBackground;
|
||||
themeConfig.value.loginBtnLinkColor = res.data?.loginBtnLinkColor;
|
||||
themeConfig.value.favicon = res.data.favicon;
|
||||
watermarkShow.value = res.data.watermarkShow === 'Enable';
|
||||
try {
|
||||
globalStore.watermark = JSON.parse(res.data.watermark);
|
||||
watermark.value = JSON.parse(res.data.watermark);
|
||||
} catch {
|
||||
globalStore.watermark = null;
|
||||
watermark.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
const link = (document.querySelector("link[rel*='icon']") || document.createElement('link')) as HTMLLinkElement;
|
||||
link.type = 'image/x-icon';
|
||||
link.rel = 'shortcut icon';
|
||||
link.href = globalStore.themeConfig.favicon ? `/api/v2/images/favicon?t=${Date.now()}` : '/public/favicon.png';
|
||||
link.href = themeConfig.value.favicon ? `/api/v2/images/favicon?t=${Date.now()}` : '/public/favicon.png';
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
};
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import { getCurrentScope, onScopeDispose } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { setPrimaryColor } from '@/utils/theme';
|
||||
|
||||
export const useTheme = () => {
|
||||
const switchTheme = () => {
|
||||
const globalStore = GlobalStore();
|
||||
const themeConfig = globalStore.themeConfig;
|
||||
let itemTheme = themeConfig.theme;
|
||||
const { isXpackOrEE, themeConfig } = useGlobalStore();
|
||||
let itemTheme = themeConfig.value.theme;
|
||||
if (itemTheme === 'auto') {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
itemTheme = prefersDark ? 'dark' : 'light';
|
||||
}
|
||||
document.documentElement.className = itemTheme === 'dark' ? 'dark' : 'light';
|
||||
if (globalStore.isXpackOrEE() && themeConfig.themeColor) {
|
||||
if (isXpackOrEE.value && themeConfig.value.themeColor) {
|
||||
try {
|
||||
const themeColor = JSON.parse(themeConfig.themeColor);
|
||||
const themeColor = JSON.parse(themeConfig.value.themeColor);
|
||||
const color = itemTheme === 'dark' ? themeColor.dark : themeColor.light;
|
||||
|
||||
if (color) {
|
||||
themeConfig.primary = color;
|
||||
themeConfig.value.primary = color;
|
||||
setPrimaryColor(color);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -29,8 +28,9 @@ export const useTheme = () => {
|
||||
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const onSystemThemeChange = () => {
|
||||
const globalStore = GlobalStore();
|
||||
if (globalStore.themeConfig.theme === 'auto') {
|
||||
const { themeConfig } = useGlobalStore();
|
||||
|
||||
if (themeConfig.value.theme === 'auto') {
|
||||
switchTheme();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="footer" :style="{ height: isMobile ? '108px' : '48px' }">
|
||||
<div class="flex w-full flex-col gap-4 md:justify-between md:flex-row">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<a v-if="!globalStore.isIntl && !globalStore.isFxplay" href="https://fit2cloud.com/" target="_blank">
|
||||
<a v-if="!isIntl && !isFxplay" href="https://fit2cloud.com/" target="_blank">
|
||||
Copyright © 2014-{{ year }} {{ $t('commons.fit2cloud') }}
|
||||
</a>
|
||||
<a v-else href="https://1panel.pro/" target="_blank">
|
||||
@@ -18,11 +18,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import SystemUpgrade from '@/components/system-upgrade/index.vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const { isMobile } = useGlobalStore();
|
||||
const globalStore = GlobalStore();
|
||||
const { isFxplay, isIntl, isMobile } = useGlobalStore();
|
||||
|
||||
const year = new Date().getFullYear();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -87,12 +87,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { GlobalStore, MenuStore } from '@/store';
|
||||
import { MenuStore } from '@/store';
|
||||
import { countExecutingTask } from '@/api/modules/log';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import i18n from '@/lang';
|
||||
import { getAgentSettingInfo } from '@/api/modules/setting';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import bus from '@/global/bus';
|
||||
import { logOutApi } from '@/api/modules/auth';
|
||||
import router from '@/routers';
|
||||
@@ -102,10 +102,12 @@ import { changeToLocal, listNodes, setDefaultNodeInfo } from '@/utils/node';
|
||||
import { Login } from '@/api/interface/auth';
|
||||
import { syncAuthInfo } from '@/utils/rbac';
|
||||
import UserInfo from './user-info/index.vue';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const filter = ref();
|
||||
const currentUser = ref<Login.AuthInfo>();
|
||||
const globalStore = GlobalStore();
|
||||
const { globalStore, currentNode, currentNodeAddr, defaultNetwork, entrance, isEnterprise, isXpackOrEE } =
|
||||
useGlobalStore();
|
||||
const menuStore = MenuStore();
|
||||
const nodes = ref([]);
|
||||
const nodeOptions = ref([]);
|
||||
@@ -115,9 +117,6 @@ const userInfoRef = ref();
|
||||
const props = defineProps({
|
||||
version: String,
|
||||
});
|
||||
const isXpackOrEE = computed(() => {
|
||||
return globalStore.isXpackOrEE();
|
||||
});
|
||||
|
||||
const emit = defineEmits(['openTask', 'refresh']);
|
||||
bus.on('refreshTask', () => {
|
||||
@@ -125,11 +124,11 @@ bus.on('refreshTask', () => {
|
||||
});
|
||||
|
||||
const loadCurrentName = () => {
|
||||
if (globalStore.currentNode) {
|
||||
if (globalStore.currentNode === 'local') {
|
||||
if (currentNode.value) {
|
||||
if (currentNode.value === 'local') {
|
||||
return globalStore.getMasterAlias();
|
||||
}
|
||||
return globalStore.currentNode;
|
||||
return currentNode.value;
|
||||
}
|
||||
return globalStore.getMasterAlias();
|
||||
};
|
||||
@@ -177,7 +176,7 @@ const loadNodes = async () => {
|
||||
});
|
||||
};
|
||||
const changeNode = async (command: string) => {
|
||||
if (globalStore.currentNode === command || switchingNode.value) {
|
||||
if (currentNode.value === command || switchingNode.value) {
|
||||
return;
|
||||
}
|
||||
switchingNode.value = true;
|
||||
@@ -185,12 +184,12 @@ const changeNode = async (command: string) => {
|
||||
for (const item of nodes.value) {
|
||||
if (item.name == command) {
|
||||
if (command == 'local') {
|
||||
if (globalStore.isEnterprise) {
|
||||
if (isEnterprise.value) {
|
||||
await loadCurrentUser('local');
|
||||
}
|
||||
await loadGlobalSetting('local');
|
||||
globalStore.currentNode = 'local';
|
||||
globalStore.currentNodeAddr = item.addr;
|
||||
currentNode.value = 'local';
|
||||
currentNodeAddr.value = item.addr;
|
||||
localStorage.removeItem('dashboardCache');
|
||||
localStorage.removeItem('upgradeChecked');
|
||||
menuStore.setMenuList([]);
|
||||
@@ -214,9 +213,9 @@ const changeNode = async (command: string) => {
|
||||
await loadGlobalSetting(command);
|
||||
localStorage.removeItem('dashboardCache');
|
||||
localStorage.removeItem('upgradeChecked');
|
||||
globalStore.currentNode = command;
|
||||
globalStore.currentNodeAddr = item.addr;
|
||||
if (globalStore.isEnterprise) {
|
||||
currentNode.value = command;
|
||||
currentNodeAddr.value = item.addr;
|
||||
if (isEnterprise.value) {
|
||||
await loadCurrentUser(command);
|
||||
}
|
||||
menuStore.setMenuList([]);
|
||||
@@ -233,7 +232,7 @@ const changeNode = async (command: string) => {
|
||||
|
||||
const loadGlobalSetting = async (currentNode?: string) => {
|
||||
await getAgentSettingInfo(currentNode).then((res) => {
|
||||
globalStore.defaultNetwork = res.data.defaultNetwork;
|
||||
defaultNetwork.value = res.data.defaultNetwork;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -267,7 +266,7 @@ const logout = () => {
|
||||
await logOutApi();
|
||||
globalStore.setLogStatus(false);
|
||||
globalStore.clearAuthInfo();
|
||||
router.push({ name: 'entrance', params: { code: globalStore.entrance } });
|
||||
router.push({ name: 'entrance', params: { code: entrance.value } });
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="logo" style="cursor: pointer" @click="goHome">
|
||||
<template v-if="isCollapse">
|
||||
<img
|
||||
v-if="globalStore.themeConfig.logo && !logoLoadFailed"
|
||||
v-if="themeConfig.logo && !logoLoadFailed"
|
||||
:src="`/api/v2/images/logo?t=${Date.now()}`"
|
||||
style="cursor: pointer"
|
||||
alt="logo"
|
||||
@@ -12,7 +12,7 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<img
|
||||
v-if="globalStore.themeConfig.logoWithText && !logoWithTextLoadFailed"
|
||||
v-if="themeConfig.logoWithText && !logoWithTextLoadFailed"
|
||||
:src="`/api/v2/images/logoWithText?t=${Date.now()}`"
|
||||
style="cursor: pointer"
|
||||
alt="logo"
|
||||
@@ -24,7 +24,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import PrimaryLogo from '@/assets/images/1panel-logo.svg?component';
|
||||
import MenuLogo from '@/assets/images/1panel-menu-logo.svg?component';
|
||||
import { ref } from 'vue';
|
||||
@@ -34,7 +34,7 @@ defineProps<{ isCollapse: boolean }>();
|
||||
|
||||
const logoLoadFailed = ref(false);
|
||||
const logoWithTextLoadFailed = ref(false);
|
||||
const globalStore = GlobalStore();
|
||||
const { themeConfig } = useGlobalStore();
|
||||
|
||||
const goHome = () => {
|
||||
routerToNameWithQuery('home', { t: Date.now() });
|
||||
|
||||
@@ -116,8 +116,8 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-form v-if="globalStore.isAdmin" label-position="top" :model="form" class="setting-section">
|
||||
<el-form-item v-if="globalStore.isAdmin">
|
||||
<el-form v-if="isAdmin" label-position="top" :model="form" class="setting-section">
|
||||
<el-form-item v-if="isAdmin">
|
||||
<template #label>
|
||||
<span class="label-with-help">
|
||||
{{ $t('setting.apiInterface') }}
|
||||
@@ -135,7 +135,7 @@
|
||||
{{ $t('setting.apiInterfaceAlert3') }}
|
||||
</el-link>
|
||||
</li>
|
||||
<li v-if="!globalStore.isFxplay">
|
||||
<li v-if="!isFxplay">
|
||||
<el-link :href="panelURL" target="_blank" class="tooltip-help-link">
|
||||
{{ $t('setting.apiInterfaceAlert4') }}
|
||||
</el-link>
|
||||
@@ -407,7 +407,7 @@ import {
|
||||
import { Setting } from '@/api/interface/setting';
|
||||
import { getSettingBaseInfo, getSettingInfo, updateSetting } from '@/api/modules/setting';
|
||||
import i18n from '@/lang';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { base64UrlToBuffer, bufferToBase64Url } from '@/utils/auth';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { checkNumberRange, Rules } from '@/global/form-rules';
|
||||
@@ -417,7 +417,7 @@ const props = defineProps<{ currentUser?: Login.AuthInfo }>();
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const complexityVerification = ref(false);
|
||||
const globalStore = GlobalStore();
|
||||
const { globalStore, docsUrl, entrance, isAdmin, isFxplay } = useGlobalStore();
|
||||
const router = useRouter();
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
@@ -443,7 +443,7 @@ const passkeyMaxCount = 5;
|
||||
const apiURL = `${window.location.protocol}//${window.location.hostname}${
|
||||
window.location.port ? `:${window.location.port}` : ''
|
||||
}/1panel/swagger/index.html`;
|
||||
const panelURL = `${globalStore.docsUrl}/dev_manual/api_manual/`;
|
||||
const panelURL = `${docsUrl.value}/dev_manual/api_manual/`;
|
||||
const form = reactive({
|
||||
id: 0,
|
||||
name: '',
|
||||
@@ -875,7 +875,7 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (needReLogin) {
|
||||
globalStore.setLogStatus(false);
|
||||
globalStore.clearAuthInfo();
|
||||
router.push({ name: 'entrance', params: { code: globalStore.entrance } });
|
||||
router.push({ name: 'entrance', params: { code: entrance.value } });
|
||||
return;
|
||||
}
|
||||
emit('search');
|
||||
|
||||
@@ -35,14 +35,15 @@ import Logo from './components/Logo.vue';
|
||||
import Collapse from './components/Collapse.vue';
|
||||
import SubItem from './components/SubItem.vue';
|
||||
import { menuList } from '@/routers/router';
|
||||
import { GlobalStore, MenuStore } from '@/store';
|
||||
import { MenuStore } from '@/store';
|
||||
import { getSettingBaseInfo } from '@/api/modules/setting';
|
||||
import PrimaryMenu from '@/assets/images/menu-bg.svg?component';
|
||||
import { hasPermission, hasRouteRoleAccess } from '@/utils/rbac';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const route = useRoute();
|
||||
const menuStore = MenuStore();
|
||||
const globalStore = GlobalStore();
|
||||
const { currentNode, isAdmin, isEE, isIntl, permissions } = useGlobalStore();
|
||||
const version = ref();
|
||||
|
||||
const activeMenu = computed(() => {
|
||||
@@ -96,29 +97,18 @@ const search = async () => {
|
||||
version.value = '';
|
||||
}
|
||||
|
||||
if (!globalStore.isAdmin) {
|
||||
menuStore.setMenuList(buildAuthVisibleMenuList(menuList));
|
||||
if (!settingInfo?.hideMenu) {
|
||||
setFallbackMenuListIfEmpty();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const hideMenu = JSON.parse(settingInfo?.hideMenu || '[]');
|
||||
const showSet = new Set<string>();
|
||||
getCheckedLabels(hideMenu, showSet);
|
||||
const rstMenuList: RouteRecordRaw[] = [];
|
||||
const resMenuList = adjustAndCleanMenu(hideMenu, menuList);
|
||||
for (const menu of resMenuList) {
|
||||
const menuItem = buildVisibleMenu(menu, showSet);
|
||||
if (menuItem) {
|
||||
rstMenuList.push(menuItem);
|
||||
}
|
||||
}
|
||||
const rstMenuList = buildMenuListFromSettings(settingInfo.hideMenu);
|
||||
if (!isSameMenuList(menuStore.menuList as RouteRecordRaw[], rstMenuList)) {
|
||||
menuStore.setMenuList(rstMenuList);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!menuStore.menuList || menuStore.menuList.length === 0) {
|
||||
menuStore.setMenuList(buildAuthVisibleMenuList(menuList));
|
||||
}
|
||||
setFallbackMenuListIfEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -126,8 +116,14 @@ function isSameMenuList(source: RouteRecordRaw[], target: RouteRecordRaw[]) {
|
||||
return JSON.stringify(source) === JSON.stringify(target);
|
||||
}
|
||||
|
||||
function setFallbackMenuListIfEmpty() {
|
||||
if (!menuStore.menuList || menuStore.menuList.length === 0) {
|
||||
menuStore.setMenuList(buildAuthVisibleMenuList(menuList));
|
||||
}
|
||||
}
|
||||
|
||||
function allowMenuItem(item: RouteRecordRaw) {
|
||||
if (globalStore.isAdmin) {
|
||||
if (isAdmin.value) {
|
||||
return true;
|
||||
}
|
||||
if (!hasRouteRoleAccess(item.meta)) {
|
||||
@@ -141,6 +137,21 @@ function allowMenuItem(item: RouteRecordRaw) {
|
||||
return allowed;
|
||||
}
|
||||
|
||||
function buildMenuListFromSettings(hideMenuValue?: string) {
|
||||
const hideMenu = JSON.parse(hideMenuValue || '[]');
|
||||
const showSet = new Set<string>();
|
||||
getCheckedLabels(hideMenu, showSet);
|
||||
const rstMenuList: RouteRecordRaw[] = [];
|
||||
const resMenuList = adjustAndCleanMenu(hideMenu, menuList);
|
||||
for (const menu of resMenuList) {
|
||||
const menuItem = buildVisibleMenu(menu, showSet);
|
||||
if (menuItem) {
|
||||
rstMenuList.push(menuItem);
|
||||
}
|
||||
}
|
||||
return rstMenuList;
|
||||
}
|
||||
|
||||
function buildAuthVisibleMenuList(source: RouteRecordRaw[]) {
|
||||
return source
|
||||
.map((item) => {
|
||||
@@ -189,7 +200,7 @@ function buildVisibleMenu(menu: RouteRecordRaw, showSet: Set<string>): RouteReco
|
||||
|
||||
const visibleChildren = children
|
||||
.map((item) => {
|
||||
if (item.name === 'Upage' && (globalStore.isIntl || globalStore.isEE())) {
|
||||
if (item.name === 'Upage' && (isIntl.value || isEE.value)) {
|
||||
return null;
|
||||
}
|
||||
return buildVisibleMenu(item, showSet);
|
||||
@@ -267,13 +278,13 @@ function adjustAndCleanMenu(menuItem, list) {
|
||||
|
||||
onMounted(() => {
|
||||
if (!menuStore.menuList || menuStore.menuList.length === 0) {
|
||||
menuStore.setMenuList(globalStore.isAdmin ? menuList : buildAuthVisibleMenuList(menuList));
|
||||
menuStore.setMenuList(isAdmin.value ? menuList : buildAuthVisibleMenuList(menuList));
|
||||
}
|
||||
search();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [globalStore.currentNode, globalStore.permissions.join('|')],
|
||||
() => [currentNode.value, permissions.value.join('|')],
|
||||
() => {
|
||||
search();
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { watch, onBeforeMount, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { GlobalStore, MenuStore } from '@/store';
|
||||
import { MenuStore } from '@/store';
|
||||
import { DeviceType } from '@/enums/app';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
/** 参考 Bootstrap 的响应式设计 WIDTH = 600 */
|
||||
@@ -9,7 +9,7 @@ const WIDTH = 600;
|
||||
/** 根据大小变化重新布局 */
|
||||
export default () => {
|
||||
const route = useRoute();
|
||||
const globalStore = GlobalStore();
|
||||
const { globalStore } = useGlobalStore();
|
||||
const menuStore = MenuStore();
|
||||
const { isMobile } = useGlobalStore();
|
||||
const _isMobile = () => {
|
||||
|
||||
@@ -19,33 +19,33 @@
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</el-affix>
|
||||
<div class="app-sidebar" v-if="!globalStore.isFullScreen">
|
||||
<div class="app-sidebar" v-if="!isFullScreen">
|
||||
<Sidebar @menu-click="handleMenuClick" :menu-router="!classObj.openMenuTabs" @open-task="openTask" />
|
||||
</div>
|
||||
|
||||
<el-watermark
|
||||
v-if="globalStore.isXpackOrEE() && globalStore.watermarkShow && globalStore.watermark"
|
||||
v-if="isXpackOrEE && watermarkShow && watermark"
|
||||
:content="loadContent()"
|
||||
:font="{
|
||||
fontSize: globalStore.watermark.fontSize,
|
||||
color: globalStore.isDarkTheme ? globalStore.watermark.darkColor : globalStore.watermark.lightColor,
|
||||
fontSize: watermark.fontSize,
|
||||
color: isDarkTheme ? watermark.darkColor : watermark.lightColor,
|
||||
textBaseline: 'top',
|
||||
}"
|
||||
:rotate="globalStore.watermark.rotate"
|
||||
:gap="[globalStore.watermark.gap, globalStore.watermark.gap]"
|
||||
:rotate="watermark.rotate"
|
||||
:gap="[watermark.gap, watermark.gap]"
|
||||
>
|
||||
<div class="main-container">
|
||||
<mobile-header v-if="classObj.mobile" />
|
||||
<Tabs v-if="classObj.openMenuTabs" />
|
||||
<app-main :keep-alive="classObj.openMenuTabs ? tabsStore.cachedTabs : null" class="app-main" />
|
||||
<Footer class="app-footer" v-if="!globalStore.isFullScreen" />
|
||||
<Footer class="app-footer" v-if="!isFullScreen" />
|
||||
</div>
|
||||
</el-watermark>
|
||||
<div class="main-container" v-else>
|
||||
<mobile-header v-if="classObj.mobile" />
|
||||
<Tabs v-if="classObj.openMenuTabs" />
|
||||
<app-main :keep-alive="classObj.openMenuTabs ? tabsStore.cachedTabs : null" class="app-main" />
|
||||
<Footer class="app-footer" v-if="!globalStore.isFullScreen" />
|
||||
<Footer class="app-footer" v-if="!isFullScreen" />
|
||||
</div>
|
||||
<TaskList ref="taskListRef" />
|
||||
</div>
|
||||
@@ -55,7 +55,7 @@
|
||||
import { onMounted, computed, ref, watch, onBeforeUnmount } from 'vue';
|
||||
import { Sidebar, Footer, AppMain, MobileHeader, Tabs } from './components';
|
||||
import useResize from './hooks/useResize';
|
||||
import { GlobalStore, MenuStore, TabsStore } from '@/store';
|
||||
import { MenuStore, TabsStore } from '@/store';
|
||||
import { getSystemAvailable } from '@/api/modules/setting';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { loadMasterProductProFromDB, loadProductProFromDB } from '@/utils/xpack';
|
||||
@@ -64,7 +64,21 @@ import TaskList from '@/components/task-list/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const { isMobile } = useGlobalStore();
|
||||
const {
|
||||
globalStore,
|
||||
currentNode,
|
||||
currentNodeAddr,
|
||||
entrance,
|
||||
isDarkTheme,
|
||||
isFullScreen,
|
||||
isLoading,
|
||||
isMobile,
|
||||
isXpackOrEE,
|
||||
loadingText: globalLoadingText,
|
||||
openMenuTabs,
|
||||
watermark,
|
||||
watermarkShow,
|
||||
} = useGlobalStore();
|
||||
const { switchTheme } = useTheme();
|
||||
|
||||
useResize();
|
||||
@@ -79,23 +93,23 @@ const openTask = () => {
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const menuStore = MenuStore();
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const tabsStore = TabsStore();
|
||||
|
||||
const loading = ref(false);
|
||||
const loadingText = computed(() =>
|
||||
globalStore.loadingText ? i18n.global.t(`commons.loadingText.${globalStore.loadingText}`) : '',
|
||||
globalLoadingText.value ? i18n.global.t(`commons.loadingText.${globalLoadingText.value}`) : '',
|
||||
);
|
||||
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
|
||||
const classObj = computed(() => {
|
||||
return {
|
||||
fullScreen: globalStore.isFullScreen,
|
||||
fullScreen: isFullScreen.value,
|
||||
hideSidebar: menuStore.isCollapse,
|
||||
openSidebar: !menuStore.isCollapse,
|
||||
mobile: isMobile.value,
|
||||
openMenuTabs: globalStore.openMenuTabs,
|
||||
openMenuTabs: openMenuTabs.value,
|
||||
withoutAnimation: menuStore.withoutAnimation,
|
||||
};
|
||||
});
|
||||
@@ -108,26 +122,25 @@ const handleCollapse = () => {
|
||||
};
|
||||
|
||||
const loadContent = () => {
|
||||
const watermark = globalStore.watermark;
|
||||
if (!watermark) {
|
||||
if (!watermark.value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let itemName = watermark.content.replaceAll(
|
||||
let itemName = watermark.value.content.replaceAll(
|
||||
'${nodeName}',
|
||||
globalStore.currentNode === 'local' ? globalStore.getMasterAlias() : globalStore.currentNode,
|
||||
currentNode.value === 'local' ? globalStore.getMasterAlias() : currentNode.value,
|
||||
);
|
||||
itemName = itemName.replaceAll('${nodeAddr}', globalStore.currentNodeAddr || '127.0.0.1');
|
||||
itemName = itemName.replaceAll('${nodeAddr}', currentNodeAddr.value || '127.0.0.1');
|
||||
return itemName;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => globalStore.isLoading,
|
||||
() => isLoading.value,
|
||||
() => {
|
||||
if (globalStore.isLoading) {
|
||||
if (isLoading.value) {
|
||||
loadStatus();
|
||||
} else {
|
||||
loading.value = globalStore.isLoading;
|
||||
loading.value = isLoading.value;
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -140,8 +153,8 @@ const handleMenuClick = async (path) => {
|
||||
const toLogin = () => {
|
||||
let baseUrl = window.location.origin;
|
||||
let newUrl = '';
|
||||
if (globalStore.entrance) {
|
||||
newUrl = baseUrl + '/' + globalStore.entrance;
|
||||
if (entrance.value) {
|
||||
newUrl = baseUrl + '/' + entrance.value;
|
||||
} else {
|
||||
newUrl = baseUrl + '/login';
|
||||
}
|
||||
@@ -149,7 +162,7 @@ const toLogin = () => {
|
||||
};
|
||||
|
||||
const loadStatus = async () => {
|
||||
loading.value = globalStore.isLoading;
|
||||
loading.value = isLoading.value;
|
||||
if (loading.value) {
|
||||
timer = setInterval(async () => {
|
||||
await getSystemAvailable()
|
||||
@@ -173,14 +186,14 @@ onBeforeUnmount(() => {
|
||||
timer = null;
|
||||
});
|
||||
onMounted(() => {
|
||||
if (globalStore.openMenuTabs && !tabsStore.activeTabPath) {
|
||||
if (openMenuTabs.value && !tabsStore.activeTabPath) {
|
||||
handleMenuClick('/');
|
||||
}
|
||||
|
||||
loadStatus();
|
||||
loadProductProFromDB();
|
||||
loadMasterProductProFromDB();
|
||||
globalStore.isFullScreen = false;
|
||||
isFullScreen.value = false;
|
||||
|
||||
const mqList = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
if (mqList.addEventListener) {
|
||||
|
||||
@@ -35,9 +35,9 @@ const bootstrap = async () => {
|
||||
app.component(key, Icons[key as keyof typeof Icons]);
|
||||
});
|
||||
|
||||
app.use(pinia);
|
||||
app.use(router);
|
||||
app.use(i18n);
|
||||
app.use(pinia);
|
||||
app.use(Components);
|
||||
app.use(directives);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import router from '@/routers/router';
|
||||
import NProgress from '@/config/nprogress';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { AxiosCanceler } from '@/api/helper/axios-cancel';
|
||||
import { hasRouteAccess } from '@/utils/rbac';
|
||||
import { loadProductProFromDB } from '@/utils/xpack';
|
||||
@@ -13,26 +13,27 @@ let isRedirecting = false;
|
||||
const enterpriseLicenseCheckWhiteList = ['EnterpriseLicenseRequired', 'entrance', 'login', 'Expired'];
|
||||
|
||||
const clearLicenseStatus = () => {
|
||||
const globalStore = GlobalStore();
|
||||
globalStore.isEnterpriseLicensed = false;
|
||||
globalStore.isEnterpriseLicenseLoaded = false;
|
||||
const { isEnterpriseLicenseLoaded, isEnterpriseLicensed } = useGlobalStore();
|
||||
isEnterpriseLicensed.value = false;
|
||||
isEnterpriseLicenseLoaded.value = false;
|
||||
};
|
||||
|
||||
const clearLoginStatus = () => {
|
||||
const globalStore = GlobalStore();
|
||||
const { globalStore } = useGlobalStore();
|
||||
globalStore.setLogStatus(false);
|
||||
globalStore.clearAuthInfo();
|
||||
clearLicenseStatus();
|
||||
};
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const { entrance, isEnterprise, isEnterpriseLicenseLoaded, isEnterpriseLicensed, isLogin } = useGlobalStore();
|
||||
NProgress.start();
|
||||
axiosCanceler.removeAllPending();
|
||||
const globalStore = GlobalStore();
|
||||
if (!globalStore.isLogin) {
|
||||
|
||||
if (!isLogin.value) {
|
||||
clearLoginStatus();
|
||||
}
|
||||
if (to.name !== 'entrance' && !globalStore.isLogin) {
|
||||
if (to.name !== 'entrance' && !isLogin.value) {
|
||||
next({
|
||||
name: 'entrance',
|
||||
params: to.params,
|
||||
@@ -40,8 +41,8 @@ router.beforeEach(async (to, from, next) => {
|
||||
NProgress.done();
|
||||
return;
|
||||
}
|
||||
if (to.name === 'entrance' && globalStore.isLogin) {
|
||||
if (to.params.code === globalStore.entrance) {
|
||||
if (to.name === 'entrance' && isLogin.value) {
|
||||
if (to.params.code === entrance.value) {
|
||||
next({
|
||||
name: 'home',
|
||||
});
|
||||
@@ -52,18 +53,18 @@ router.beforeEach(async (to, from, next) => {
|
||||
NProgress.done();
|
||||
return;
|
||||
}
|
||||
if (globalStore.isLogin && globalStore.isEnterprise && !enterpriseLicenseCheckWhiteList.includes(String(to.name))) {
|
||||
if (!globalStore.isEnterpriseLicenseLoaded) {
|
||||
if (isLogin.value && isEnterprise.value && !enterpriseLicenseCheckWhiteList.includes(String(to.name))) {
|
||||
if (!isEnterpriseLicenseLoaded.value) {
|
||||
await loadProductProFromDB();
|
||||
}
|
||||
if (!globalStore.isEnterpriseLicensed) {
|
||||
if (!isEnterpriseLicensed.value) {
|
||||
next({ name: 'EnterpriseLicenseRequired', query: { code: String(to.params.code || '') } });
|
||||
NProgress.done();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (to.name === 'EnterpriseLicenseRequired') {
|
||||
if (!globalStore.isLogin) {
|
||||
if (!isLogin.value) {
|
||||
next({
|
||||
name: 'entrance',
|
||||
params: to.params,
|
||||
@@ -71,7 +72,7 @@ router.beforeEach(async (to, from, next) => {
|
||||
NProgress.done();
|
||||
return;
|
||||
}
|
||||
if (!globalStore.isEnterprise || globalStore.isEnterpriseLicensed) {
|
||||
if (!isEnterprise.value || isEnterpriseLicensed.value) {
|
||||
next({ name: 'home' });
|
||||
NProgress.done();
|
||||
return;
|
||||
|
||||
@@ -9,6 +9,7 @@ const containerRouter = {
|
||||
meta: {
|
||||
icon: 'p-docker1',
|
||||
title: 'menu.container',
|
||||
permission: 'container_view',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ const cronRouter = {
|
||||
meta: {
|
||||
icon: 'p-plan',
|
||||
title: 'menu.cronjob',
|
||||
permission: 'cronjob_view',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ const databaseRouter = {
|
||||
meta: {
|
||||
icon: 'p-database',
|
||||
title: 'menu.database',
|
||||
permission: 'database_view',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ const webSiteRouter = {
|
||||
meta: {
|
||||
icon: 'p-website',
|
||||
title: 'menu.website',
|
||||
permission: 'website_view',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
|
||||
@@ -78,6 +78,9 @@ const GlobalStore = defineStore({
|
||||
isDarkGoldTheme: (state) => state.themeConfig.primary === '#F0BE96' && state.isProductPro,
|
||||
isNodeAdmin: (state) =>
|
||||
state.nodeRoles.some((item) => item.nodeName === state.currentNode && item.roleName === 'Node Admin'),
|
||||
isAdminOrNodeAdmin: (state) =>
|
||||
state.isAdmin ||
|
||||
state.nodeRoles.some((item) => item.nodeName === state.currentNode && item.roleName === 'Node Admin'),
|
||||
docsUrl: (state) => {
|
||||
if (state.docWithRegion) {
|
||||
return state.isIntl ? INTL_DOCS_URL : CN_DOCS_URL;
|
||||
@@ -88,6 +91,12 @@ const GlobalStore = defineStore({
|
||||
},
|
||||
isMaster: (state) => state.currentNode === 'local',
|
||||
isMobile: (state) => state.device === DeviceType.Mobile,
|
||||
|
||||
isXpackOrEE: (state) => {
|
||||
return (state.isEnterprise && state.isEnterpriseLicensed) || state.isMasterProductPro;
|
||||
},
|
||||
isEE: (state) => state.isEnterprise && state.isEnterpriseLicensed,
|
||||
isMasterPro: (state) => state.isMasterProductPro,
|
||||
},
|
||||
actions: {
|
||||
setScreenFull() {
|
||||
@@ -144,15 +153,6 @@ const GlobalStore = defineStore({
|
||||
getMasterAlias() {
|
||||
return this.masterAlias || i18n.global.t('xpack.node.master');
|
||||
},
|
||||
isEE() {
|
||||
return this.isEnterprise && this.isEnterpriseLicensed;
|
||||
},
|
||||
isXpackOrEE() {
|
||||
return (this.isEnterprise && this.isEnterpriseLicensed) || this.isMasterProductPro;
|
||||
},
|
||||
isMasterPro() {
|
||||
return this.isMasterProductPro;
|
||||
},
|
||||
},
|
||||
persist: piniaPersistConfig('GlobalState'),
|
||||
});
|
||||
|
||||
@@ -2,9 +2,8 @@ import { jumpToPath } from './router';
|
||||
import router from '@/routers';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const { isProductPro } = useGlobalStore();
|
||||
|
||||
export const jumpToInstall = (type: string, key: string) => {
|
||||
const { isProductPro } = useGlobalStore();
|
||||
switch (type) {
|
||||
case 'php':
|
||||
case 'node':
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ResultEnum } from '@/enums/http-enum';
|
||||
import i18n from '@/lang';
|
||||
import router from '@/routers';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { MsgError } from '@/utils/message';
|
||||
|
||||
export type AuthResponseAction = 'reject' | 'return';
|
||||
@@ -19,11 +19,11 @@ interface AuthResponseOptions {
|
||||
const forbiddenMessage = () => i18n.global.t('commons.res.forbidden');
|
||||
|
||||
export const redirectToEntrance = () => {
|
||||
const globalStore = GlobalStore();
|
||||
globalStore.isLogin = false;
|
||||
const { entrance, isLogin } = useGlobalStore();
|
||||
isLogin.value = false;
|
||||
router.push({
|
||||
name: 'entrance',
|
||||
params: { code: globalStore.entrance },
|
||||
params: { code: entrance.value },
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
+12
-14
@@ -1,33 +1,31 @@
|
||||
import { Setting } from '@/api/interface/setting';
|
||||
import { listNodeOptions, loadNodeByUser } from '@/api/modules/setting';
|
||||
import { GlobalStore } from '@/store';
|
||||
|
||||
const getGlobalStore = () => GlobalStore();
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
export const changeToLocal = async () => {
|
||||
const globalStore = getGlobalStore();
|
||||
const { currentNode, currentNodeAddr, isAdmin } = useGlobalStore();
|
||||
let nodes = await listNodes('all');
|
||||
if (nodes.length === 0) {
|
||||
setDefaultNodeInfo();
|
||||
return;
|
||||
}
|
||||
if (globalStore.isAdmin) {
|
||||
if (isAdmin.value) {
|
||||
for (const item of nodes) {
|
||||
if (item.name === 'local') {
|
||||
globalStore.currentNode = 'local';
|
||||
globalStore.currentNodeAddr = item.addr;
|
||||
currentNode.value = 'local';
|
||||
currentNodeAddr.value = item.addr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
globalStore.currentNode = nodes[0].name;
|
||||
globalStore.currentNodeAddr = nodes[0].addr;
|
||||
currentNode.value = nodes[0].name;
|
||||
currentNodeAddr.value = nodes[0].addr;
|
||||
};
|
||||
|
||||
export async function listNodes(type: string): Promise<Array<Setting.NodeItem>> {
|
||||
const globalStore = getGlobalStore();
|
||||
const { isAdmin } = useGlobalStore();
|
||||
try {
|
||||
if (globalStore.isAdmin) {
|
||||
if (isAdmin.value) {
|
||||
const res = await listNodeOptions(type);
|
||||
return res.data || [];
|
||||
} else {
|
||||
@@ -40,7 +38,7 @@ export async function listNodes(type: string): Promise<Array<Setting.NodeItem>>
|
||||
}
|
||||
|
||||
export const setDefaultNodeInfo = () => {
|
||||
const globalStore = getGlobalStore();
|
||||
globalStore.currentNode = 'local';
|
||||
globalStore.currentNodeAddr = '127.0.0.1';
|
||||
const { currentNode, currentNodeAddr } = useGlobalStore();
|
||||
currentNode.value = 'local';
|
||||
currentNodeAddr.value = '127.0.0.1';
|
||||
};
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import router from '@/routers';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
export type PermissionBindingValue = string | string[] | undefined;
|
||||
export type PermissionMode = 'manage' | 'view';
|
||||
|
||||
const getRoutePermission = () => {
|
||||
const route = router.currentRoute.value;
|
||||
const metaPermission = route.meta?.permission;
|
||||
if (typeof metaPermission === 'string' && metaPermission) {
|
||||
return metaPermission;
|
||||
}
|
||||
|
||||
for (const record of [...route.matched].reverse()) {
|
||||
const permission = record.meta?.permission;
|
||||
if (typeof permission === 'string' && permission) {
|
||||
return permission;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
export const toManagePermission = (permission: string) => {
|
||||
if (!permission) {
|
||||
return '';
|
||||
}
|
||||
return permission.endsWith('_view') ? permission.replace(/_view$/, '_manage') : permission;
|
||||
};
|
||||
|
||||
export const toPermissionList = (value: PermissionBindingValue) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.filter(
|
||||
(permission): permission is string => typeof permission === 'string' && !!permission.trim(),
|
||||
);
|
||||
}
|
||||
if (typeof value === 'string' && value.trim()) {
|
||||
return [value];
|
||||
}
|
||||
const routePermission = getRoutePermission();
|
||||
return routePermission ? [routePermission] : [];
|
||||
};
|
||||
|
||||
export const hasManagePermissionAccess = (value?: PermissionBindingValue) => {
|
||||
const { globalStore, isAdmin, isNodeAdmin } = useGlobalStore();
|
||||
if (isAdmin.value || isNodeAdmin.value) {
|
||||
return true;
|
||||
}
|
||||
const permissions = toPermissionList(value).map(toManagePermission).filter(Boolean);
|
||||
if (permissions.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return permissions.every((permission) => globalStore.hasPermission(permission));
|
||||
};
|
||||
|
||||
export const hasPermissionAccess = (value?: PermissionBindingValue) => {
|
||||
const { globalStore, isAdmin, isNodeAdmin } = useGlobalStore();
|
||||
|
||||
if (isAdmin.value || isNodeAdmin.value) {
|
||||
return true;
|
||||
}
|
||||
const permissions = toPermissionList(value);
|
||||
if (permissions.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return permissions.every((permission) => globalStore.hasPermission(permission));
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getUserInfo } from '@/api/modules/auth';
|
||||
import { getEnterpriseUserInfo } from '@/extensions/xpack';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import type { RouteMeta } from 'vue-router';
|
||||
|
||||
type RouteAccessMeta = {
|
||||
@@ -17,12 +17,12 @@ type RouteAccessTarget = {
|
||||
};
|
||||
|
||||
export const syncAuthInfo = async (currentNode?: string) => {
|
||||
const globalStore = GlobalStore();
|
||||
if (!globalStore.isEnterprise) {
|
||||
const { globalStore, currentNode: storeCurrentNode, isEnterprise } = useGlobalStore();
|
||||
if (!isEnterprise.value) {
|
||||
const res = await getUserInfo();
|
||||
return res.data;
|
||||
}
|
||||
const res = await getEnterpriseUserInfo(currentNode ?? globalStore.currentNode);
|
||||
const res = await getEnterpriseUserInfo(currentNode ?? storeCurrentNode.value);
|
||||
globalStore.setAuthInfo({
|
||||
isAdmin: res.data.role === 'ADMIN',
|
||||
permissions: res.data.permissions || [],
|
||||
@@ -32,22 +32,23 @@ export const syncAuthInfo = async (currentNode?: string) => {
|
||||
};
|
||||
|
||||
export const hasPermission = (permission: string) => {
|
||||
return GlobalStore().hasPermission(permission);
|
||||
return useGlobalStore().globalStore.hasPermission(permission);
|
||||
};
|
||||
|
||||
export const hasRouteRoleAccess = (meta?: RouteMeta & RouteAccessMeta) => {
|
||||
const globalStore = GlobalStore();
|
||||
const { isAdmin, isNodeAdmin } = useGlobalStore();
|
||||
|
||||
if (!meta) {
|
||||
return true;
|
||||
}
|
||||
if (globalStore.isAdmin) {
|
||||
if (isAdmin.value) {
|
||||
return true;
|
||||
}
|
||||
if (meta.adminOnly && !globalStore.isAdmin) {
|
||||
if (meta.adminOnly && !isAdmin.value) {
|
||||
return false;
|
||||
}
|
||||
if (meta.protectedRoleOnly) {
|
||||
return globalStore.isNodeAdmin;
|
||||
return isNodeAdmin.value;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { handleAuthResponseCode, handleAuthResponseStatus } from '@/utils/auth-response';
|
||||
|
||||
export const checkStreamAuth = async (url: string, currentNode?: string) => {
|
||||
const globalStore = GlobalStore();
|
||||
const { currentNode: storeCurrentNode, language } = useGlobalStore();
|
||||
const controller = new AbortController();
|
||||
const timeout = window.setTimeout(() => controller.abort(), 5000);
|
||||
try {
|
||||
const res = await fetch(url.replace(/^ws/, 'http'), {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept-Language': globalStore.language,
|
||||
CurrentNode: encodeURIComponent(currentNode || globalStore.currentNode),
|
||||
'Accept-Language': language.value,
|
||||
CurrentNode: encodeURIComponent(currentNode || storeCurrentNode.value),
|
||||
},
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
+55
-57
@@ -9,21 +9,18 @@ import {
|
||||
searchXpackSetting,
|
||||
updateXpackSettingByKey as updateXpackSettingByKeyFromExtension,
|
||||
} from '@/extensions/xpack';
|
||||
import { GlobalStore } from '@/store';
|
||||
const { switchTheme } = useTheme();
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import faviconUrl from '@/assets/images/favicon.svg';
|
||||
|
||||
const getGlobalStore = () => GlobalStore();
|
||||
|
||||
export function resetXSetting() {
|
||||
const globalStore = getGlobalStore();
|
||||
globalStore.themeConfig.title = '';
|
||||
globalStore.themeConfig.logo = '';
|
||||
globalStore.themeConfig.logoWithText = '';
|
||||
globalStore.themeConfig.favicon = '';
|
||||
globalStore.watermark = null;
|
||||
globalStore.watermarkShow = false;
|
||||
globalStore.masterAlias = '';
|
||||
const { masterAlias, themeConfig, watermark, watermarkShow } = useGlobalStore();
|
||||
themeConfig.value.title = '';
|
||||
themeConfig.value.logo = '';
|
||||
themeConfig.value.logoWithText = '';
|
||||
themeConfig.value.favicon = '';
|
||||
watermark.value = null;
|
||||
watermarkShow.value = false;
|
||||
masterAlias.value = '';
|
||||
}
|
||||
|
||||
async function getColoredFavicon(url: string, color: string) {
|
||||
@@ -34,11 +31,11 @@ async function getColoredFavicon(url: string, color: string) {
|
||||
}
|
||||
|
||||
export async function initFavicon() {
|
||||
const globalStore = getGlobalStore();
|
||||
document.title = globalStore.themeConfig.panelName;
|
||||
const favicon = globalStore.themeConfig.favicon;
|
||||
const isPro = globalStore.isXpackOrEE();
|
||||
const themeColor = globalStore.themeConfig.primary;
|
||||
const { isXpackOrEE, themeConfig } = useGlobalStore();
|
||||
document.title = themeConfig.value.panelName;
|
||||
const favicon = themeConfig.value.favicon;
|
||||
const isPro = isXpackOrEE.value;
|
||||
const themeColor = themeConfig.value.primary;
|
||||
const customFaviconUrl = `/api/v2/images/favicon?t=${Date.now()}`;
|
||||
const fallbackSvg = isPro ? await getColoredFavicon(faviconUrl, themeColor) : '/public/favicon.png';
|
||||
const setLink = (href: string) => {
|
||||
@@ -74,90 +71,91 @@ export async function getXpackSetting() {
|
||||
}
|
||||
|
||||
const loadDataFromDB = async () => {
|
||||
const globalStore = getGlobalStore();
|
||||
const { entrance, openMenuTabs } = useGlobalStore();
|
||||
const res = await getSettingBaseInfo();
|
||||
document.title = res.data.panelName;
|
||||
globalStore.entrance = res.data.securityEntrance;
|
||||
globalStore.openMenuTabs = res.data.menuTabs === 'Enable';
|
||||
entrance.value = res.data.securityEntrance;
|
||||
openMenuTabs.value = res.data.menuTabs === 'Enable';
|
||||
};
|
||||
|
||||
export async function loadProductProFromDB() {
|
||||
const globalStore = getGlobalStore();
|
||||
if (!globalStore.isEnterprise) {
|
||||
globalStore.isEnterpriseLicenseLoaded = true;
|
||||
const { isEnterprise, isEnterpriseLicenseLoaded, isEnterpriseLicensed, isProductPro, productProExpires } =
|
||||
useGlobalStore();
|
||||
if (!isEnterprise.value) {
|
||||
isEnterpriseLicenseLoaded.value = true;
|
||||
const res = await getLicenseStatus();
|
||||
if (!res || !res.data) {
|
||||
globalStore.isProductPro = false;
|
||||
isProductPro.value = false;
|
||||
} else {
|
||||
globalStore.isProductPro = res.data.status === 'Bound';
|
||||
if (globalStore.isProductPro) {
|
||||
globalStore.productProExpires = Number(res.data.productPro);
|
||||
isProductPro.value = res.data.status === 'Bound';
|
||||
if (isProductPro.value) {
|
||||
productProExpires.value = Number(res.data.productPro);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
const res = await getEnterpriseLicenseStatus();
|
||||
globalStore.isEnterpriseLicenseLoaded = true;
|
||||
isEnterpriseLicenseLoaded.value = true;
|
||||
if (!res || !res.data) {
|
||||
globalStore.isEnterpriseLicensed = false;
|
||||
isEnterpriseLicensed.value = false;
|
||||
} else {
|
||||
globalStore.isEnterpriseLicensed = res.data.status === 'Bound';
|
||||
globalStore.isProductPro = globalStore.isEnterpriseLicensed;
|
||||
isEnterpriseLicensed.value = res.data.status === 'Bound';
|
||||
isProductPro.value = isEnterpriseLicensed.value;
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadMasterProductProFromDB() {
|
||||
const globalStore = getGlobalStore();
|
||||
if (!globalStore.isEnterprise) {
|
||||
globalStore.isEnterpriseLicenseLoaded = true;
|
||||
const { isEnterprise, isEnterpriseLicenseLoaded, isEnterpriseLicensed, isMasterProductPro } = useGlobalStore();
|
||||
if (!isEnterprise.value) {
|
||||
isEnterpriseLicenseLoaded.value = true;
|
||||
const res = await getMasterLicenseStatus();
|
||||
if (!res || !res.data) {
|
||||
globalStore.isMasterProductPro = false;
|
||||
isMasterProductPro.value = false;
|
||||
} else {
|
||||
globalStore.isMasterProductPro = res.data.status === 'Bound';
|
||||
isMasterProductPro.value = res.data.status === 'Bound';
|
||||
}
|
||||
} else {
|
||||
const res = await getEnterpriseLicenseStatus();
|
||||
globalStore.isEnterpriseLicenseLoaded = true;
|
||||
isEnterpriseLicenseLoaded.value = true;
|
||||
if (!res || !res.data) {
|
||||
globalStore.isEnterpriseLicensed = false;
|
||||
isEnterpriseLicensed.value = false;
|
||||
} else {
|
||||
globalStore.isEnterpriseLicensed = res.data.status === 'Bound';
|
||||
globalStore.isMasterProductPro = res.data.status === 'Bound';
|
||||
isEnterpriseLicensed.value = res.data.status === 'Bound';
|
||||
isMasterProductPro.value = res.data.status === 'Bound';
|
||||
}
|
||||
}
|
||||
switchTheme();
|
||||
useTheme().switchTheme();
|
||||
initFavicon();
|
||||
loadDataFromDB();
|
||||
}
|
||||
|
||||
export async function getXpackSettingForTheme() {
|
||||
const globalStore = getGlobalStore();
|
||||
const { masterAlias, themeConfig, watermark, watermarkShow } = useGlobalStore();
|
||||
const res2 = await searchXpackSetting();
|
||||
if (res2) {
|
||||
globalStore.themeConfig.title = res2.data?.title;
|
||||
globalStore.themeConfig.logo = res2.data?.logo;
|
||||
globalStore.themeConfig.logoWithText = res2.data?.logoWithText;
|
||||
globalStore.themeConfig.favicon = res2.data?.favicon;
|
||||
globalStore.themeConfig.loginImage = res2.data?.loginImage;
|
||||
globalStore.themeConfig.loginBgType = res2.data?.loginBgType;
|
||||
globalStore.themeConfig.loginBackground = res2.data?.loginBackground;
|
||||
globalStore.themeConfig.loginBtnLinkColor = res2.data?.loginBtnLinkColor;
|
||||
globalStore.themeConfig.themeColor = res2.data?.themeColor;
|
||||
globalStore.masterAlias = res2.data.masterAlias;
|
||||
themeConfig.value.title = res2.data?.title;
|
||||
themeConfig.value.logo = res2.data?.logo;
|
||||
themeConfig.value.logoWithText = res2.data?.logoWithText;
|
||||
themeConfig.value.favicon = res2.data?.favicon;
|
||||
themeConfig.value.loginImage = res2.data?.loginImage;
|
||||
themeConfig.value.loginBgType = res2.data?.loginBgType;
|
||||
themeConfig.value.loginBackground = res2.data?.loginBackground;
|
||||
themeConfig.value.loginBtnLinkColor = res2.data?.loginBtnLinkColor;
|
||||
themeConfig.value.themeColor = res2.data?.themeColor;
|
||||
masterAlias.value = res2.data.masterAlias;
|
||||
if (res2.data?.theme) {
|
||||
globalStore.themeConfig.theme = res2.data.theme;
|
||||
themeConfig.value.theme = res2.data.theme;
|
||||
}
|
||||
globalStore.watermarkShow = res2.data.watermarkShow === 'Enable';
|
||||
watermarkShow.value = res2.data.watermarkShow === 'Enable';
|
||||
try {
|
||||
globalStore.watermark = JSON.parse(res2.data.watermark);
|
||||
watermark.value = JSON.parse(res2.data.watermark);
|
||||
} catch {
|
||||
globalStore.watermark = null;
|
||||
watermark.value = null;
|
||||
}
|
||||
} else {
|
||||
resetXSetting();
|
||||
}
|
||||
switchTheme();
|
||||
useTheme().switchTheme();
|
||||
initFavicon();
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
</el-select>
|
||||
<span class="input-help">
|
||||
{{ $t('aiTools.agents.noAccountHint') }}
|
||||
<el-button type="primary" link class="inline-link" @click="openAccountCreate">
|
||||
<el-button v-permission type="primary" link class="inline-link" @click="openAccountCreate">
|
||||
{{ $t('commons.button.create') }}
|
||||
</el-button>
|
||||
</span>
|
||||
@@ -95,7 +95,9 @@
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="open = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit">{{ $t('commons.button.confirm') }}</el-button>
|
||||
<el-button v-permission type="primary" @click="submit">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
<template v-if="editingSessionId === item.id">
|
||||
<el-button
|
||||
link
|
||||
v-permission
|
||||
type="primary"
|
||||
:disabled="!canSaveSessionTitle"
|
||||
@click.stop="saveSessionTitle(item)"
|
||||
@@ -58,6 +59,7 @@
|
||||
<el-button
|
||||
v-else-if="!terminalOpen"
|
||||
link
|
||||
v-permission
|
||||
icon="Edit"
|
||||
class="hermes-chat-dialog__edit-button"
|
||||
@mousedown.stop
|
||||
@@ -66,6 +68,7 @@
|
||||
<el-button
|
||||
v-if="editingSessionId !== item.id"
|
||||
link
|
||||
v-permission
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
:disabled="isDeleteDisabled(item)"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@close="handleClose"
|
||||
:resource="title"
|
||||
fullScreen
|
||||
:size="globalStore.isFullScreen ? 'full' : 'large'"
|
||||
:size="isFullScreen ? 'full' : 'large'"
|
||||
:autoClose="false"
|
||||
>
|
||||
<template #content>
|
||||
@@ -33,7 +33,7 @@ import { nextTick, reactive, ref } from 'vue';
|
||||
import Terminal from '@/components/terminal/index.vue';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const { globalStore, currentNode } = useGlobalStore();
|
||||
const { currentNode, isFullScreen } = useGlobalStore();
|
||||
|
||||
const title = ref('');
|
||||
const terminalVisible = ref(false);
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.operate')" width="100" align="right">
|
||||
<template #default="{ $index }">
|
||||
<el-button link @click="removeBinding($index)">
|
||||
<el-button v-permission link @click="removeBinding($index)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -45,7 +45,7 @@
|
||||
<el-empty :description="$t('commons.msg.noneData')" :image-size="60" />
|
||||
</div>
|
||||
<div class="binding-dialog__actions">
|
||||
<el-button type="primary" link :disabled="loading || submitting" @click="addBinding">
|
||||
<el-button v-permission type="primary" link :disabled="loading || submitting" @click="addBinding">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -57,6 +57,7 @@
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="submitting"
|
||||
v-permission
|
||||
:disabled="loading || !form.bindings.length"
|
||||
@click="submitBind"
|
||||
>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.operate')" width="90" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link @click="removeBinding($index)">
|
||||
<el-button v-permission link @click="removeBinding($index)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -59,7 +59,7 @@
|
||||
</el-table>
|
||||
<el-empty v-else :description="$t('commons.msg.noneData')" :image-size="60" />
|
||||
<div class="bindings-footer">
|
||||
<el-button type="primary" link @click="addBinding">
|
||||
<el-button v-permission type="primary" link @click="addBinding">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -68,7 +68,7 @@
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button :disabled="loading" @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" :loading="loading" @click="submit">
|
||||
<el-button v-permission type="primary" :loading="loading" @click="submit">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<el-button :loading="loading" :disabled="saving || !agentId || !workspace" @click="reloadFiles">
|
||||
{{ $t('commons.button.refresh') }}
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="saving" :disabled="!currentFile" @click="saveAllFiles">
|
||||
<el-button v-permission type="primary" :loading="saving" :disabled="!currentFile" @click="saveAllFiles">
|
||||
{{ $t('aiTools.agents.saveAllMd') }}
|
||||
</el-button>
|
||||
</span>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
/>
|
||||
<template v-else>
|
||||
<div class="role-toolbar">
|
||||
<el-button type="primary" @click="openCreateDialog">
|
||||
<el-button v-permission type="primary" @click="openCreateDialog">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -49,7 +49,7 @@
|
||||
{{ $t('aiTools.agents.agentDir') }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-button plain size="small" round @click="handleDelete(row)">
|
||||
<el-button plain size="small" round v-permission @click="handleDelete(row)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -64,17 +64,17 @@
|
||||
<el-tag
|
||||
v-for="item in row.bindings"
|
||||
:key="`${item.channel}-${item.accountId || 'default'}`"
|
||||
closable
|
||||
:closable="canManageCurrentAgent"
|
||||
@close="handleUnbind(row, item)"
|
||||
>
|
||||
{{ item.accountId ? `${item.channel}:${item.accountId}` : item.channel }}
|
||||
</el-tag>
|
||||
<el-button size="small" @click="openBindingDialog(row)">
|
||||
<el-button v-permission size="small" @click="openBindingDialog(row)">
|
||||
+ {{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-else class="role-card__tags">
|
||||
<el-button size="small" @click="openBindingDialog(row)">
|
||||
<el-button v-permission size="small" @click="openBindingDialog(row)">
|
||||
+ {{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -92,11 +92,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { deleteAgentRole, getConfiguredAgentRoles, unbindAgentRole } from '@/api/modules/ai';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import i18n from '@/lang';
|
||||
import { routerToFileWithPath } from '@/utils/router';
|
||||
import { hasManagePermissionAccess } from '@/utils/permission';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import CreateDialog from './create/index.vue';
|
||||
import BindingDialog from './binding/index.vue';
|
||||
@@ -120,6 +121,7 @@ const agentType = ref<AI.AgentType>('openclaw');
|
||||
const accountId = ref(0);
|
||||
const currentModel = ref('');
|
||||
const configuredAgents = ref<AI.AgentConfiguredAgentItem[]>([]);
|
||||
const canManageCurrentAgent = computed(() => hasManagePermissionAccess());
|
||||
|
||||
const loadConfiguredAgents = async (id: number) => {
|
||||
const res = await getConfiguredAgentRoles({ agentId: id });
|
||||
|
||||
+25
-10
@@ -2,7 +2,7 @@
|
||||
<div class="channel-bots">
|
||||
<div class="channel-bots__header">
|
||||
<span class="channel-bots__title">{{ title || t('aiTools.agents.bots') }}</span>
|
||||
<el-button type="primary" link :disabled="addDisabled" @click="openCreate">
|
||||
<el-button v-permission type="primary" link :disabled="addDisabled" @click="openCreate">
|
||||
{{ t('aiTools.agents.addBot') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -32,6 +32,7 @@
|
||||
>
|
||||
<template #default="{ row, $index }">
|
||||
<el-switch
|
||||
v-permission
|
||||
:model-value="row.enabled"
|
||||
:disabled="disabled || isBotActionDisabled(row)"
|
||||
@change="updateEnabled($index, $event)"
|
||||
@@ -41,11 +42,12 @@
|
||||
<el-table-column :label="t('commons.table.operate')" width="240" fixed="right">
|
||||
<template #default="{ row, $index }">
|
||||
<div class="channel-bots__actions">
|
||||
<el-button link type="primary" :disabled="disabled" @click="openEdit(row, $index)">
|
||||
<el-button v-permission link type="primary" :disabled="disabled" @click="openEdit(row, $index)">
|
||||
{{ t('commons.button.edit') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="approvable"
|
||||
v-permission
|
||||
link
|
||||
type="primary"
|
||||
:disabled="
|
||||
@@ -59,6 +61,7 @@
|
||||
{{ t('aiTools.agents.approvePairing') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-permission
|
||||
:disabled="disabled || undeletableAccountIds.includes(row.accountId)"
|
||||
link
|
||||
type="primary"
|
||||
@@ -71,18 +74,19 @@
|
||||
trigger="hover"
|
||||
@command="handleMoreCommand(row, $index, $event)"
|
||||
>
|
||||
<el-button link type="primary" :disabled="disabled">
|
||||
<el-button v-permission link type="primary" :disabled="disabled">
|
||||
{{ t('tabs.more') }}
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
<fu-dropdown-item
|
||||
v-if="defaultable && !row.isDefault"
|
||||
v-permission
|
||||
command="default"
|
||||
:disabled="disabled || isBotActionDisabled(row)"
|
||||
>
|
||||
{{ t('aiTools.agents.setDefaultBot') }}
|
||||
</el-dropdown-item>
|
||||
</fu-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
@@ -94,21 +98,23 @@
|
||||
<DialogPro v-model="dialogVisible" :title="dialogTitle">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
|
||||
<el-form-item v-if="showNameField" :label="t('commons.table.name')" prop="name">
|
||||
<el-input v-model="form.name" :disabled="disabled" />
|
||||
<el-input v-permission v-model="form.name" :disabled="disabled" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('aiTools.agents.accountId')" prop="accountId">
|
||||
<el-input
|
||||
v-permission
|
||||
v-model="form.accountId"
|
||||
:disabled="disabled || accountIdLocked"
|
||||
:placeholder="t('aiTools.agents.accountIdPlaceholder')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('commons.table.status')">
|
||||
<el-switch v-model="form.enabled" :disabled="disabled" />
|
||||
<el-switch v-permission v-model="form.enabled" :disabled="disabled" />
|
||||
</el-form-item>
|
||||
<el-form-item v-for="field in fields" :key="field.prop" :label="field.label" :prop="field.prop">
|
||||
<el-select
|
||||
v-if="field.type === 'select'"
|
||||
v-permission
|
||||
v-model="form[field.prop]"
|
||||
:disabled="disabled"
|
||||
:placeholder="field.placeholder"
|
||||
@@ -122,6 +128,7 @@
|
||||
</el-select>
|
||||
<el-input
|
||||
v-else-if="field.type === 'password'"
|
||||
v-permission
|
||||
v-model="form[field.prop]"
|
||||
type="password"
|
||||
show-password
|
||||
@@ -130,18 +137,25 @@
|
||||
/>
|
||||
<el-input
|
||||
v-else-if="field.type === 'textarea'"
|
||||
v-permission
|
||||
v-model="form[field.prop]"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
:disabled="disabled"
|
||||
:placeholder="field.placeholder"
|
||||
/>
|
||||
<el-input v-else v-model="form[field.prop]" :disabled="disabled" :placeholder="field.placeholder" />
|
||||
<el-input
|
||||
v-else
|
||||
v-permission
|
||||
v-model="form[field.prop]"
|
||||
:disabled="disabled"
|
||||
:placeholder="field.placeholder"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">{{ t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" :disabled="disabled" @click="saveBot">
|
||||
<el-button v-permission type="primary" :disabled="disabled" @click="saveBot">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -296,8 +310,9 @@ const rules = computed<FormRules>(() => {
|
||||
config[field.prop] = [Rules.requiredInput];
|
||||
}
|
||||
if (props.uniqueFieldProp) {
|
||||
const existingRules = config[props.uniqueFieldProp];
|
||||
config[props.uniqueFieldProp] = [
|
||||
...(config[props.uniqueFieldProp] || []),
|
||||
...(Array.isArray(existingRules) ? existingRules : existingRules ? [existingRules] : []),
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
const exists = props.bots.some(
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@
|
||||
:description="t('aiTools.agents.pluginInstallNPMRegistryHelper')"
|
||||
/>
|
||||
<el-form-item v-if="!installed" class="mt-4">
|
||||
<el-button type="primary" :loading="installing" @click="handleInstall">
|
||||
<el-button v-permission type="primary" :loading="installing" @click="handleInstall">
|
||||
{{ t('commons.button.install') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -18,7 +18,7 @@
|
||||
<span class="plugin-install-status__label">{{ t('app.version') }}</span>
|
||||
<span class="plugin-install-status__value">{{ currentVersion || '-' }}</span>
|
||||
</div>
|
||||
<el-button type="danger" plain size="small" :loading="uninstalling" @click="handleUninstall">
|
||||
<el-button type="danger" plain size="small" :loading="uninstalling" v-permission @click="handleUninstall">
|
||||
{{ t('commons.button.uninstall') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -27,7 +27,7 @@
|
||||
<span class="plugin-install-status__label">{{ t('app.newVersion') }}</span>
|
||||
<span class="plugin-install-status__value">{{ latestVersion || '-' }}</span>
|
||||
</div>
|
||||
<el-button type="primary" size="small" :loading="upgrading" @click="handleUpgrade">
|
||||
<el-button type="primary" size="small" :loading="upgrading" v-permission @click="handleUpgrade">
|
||||
{{ t('commons.button.upgrade') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-form ref="formRef" v-loading="deleting" :model="form" :rules="rules" label-position="top">
|
||||
<el-form-item v-if="configured">
|
||||
<el-button type="danger" plain :loading="deleting" @click="deleteChannel">
|
||||
<el-button v-permission type="danger" plain :loading="deleting" @click="deleteChannel">
|
||||
{{ t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -19,7 +19,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="save">
|
||||
<el-button v-permission type="primary" :loading="saving" @click="save">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -29,7 +29,7 @@
|
||||
<el-input v-model="pairingCode" :placeholder="t('aiTools.agents.pairingCodePlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain :loading="approving" @click="approvePairing">
|
||||
<el-button v-permission type="primary" plain :loading="approving" @click="approvePairing">
|
||||
{{ t('aiTools.agents.approvePairing') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-form ref="formRef" v-loading="deleting" :model="form" :rules="rules" label-position="top">
|
||||
<el-form-item v-if="configured">
|
||||
<el-button type="danger" plain :loading="deleting" @click="deleteChannel">
|
||||
<el-button v-permission type="danger" plain :loading="deleting" @click="deleteChannel">
|
||||
{{ t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -18,7 +18,7 @@
|
||||
<el-switch v-model="form.requireMention" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="save">
|
||||
<el-button v-permission type="primary" :loading="saving" @click="save">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -28,7 +28,7 @@
|
||||
<el-input v-model="pairingCode" :placeholder="t('aiTools.agents.pairingCodePlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain :loading="approving" @click="approvePairing">
|
||||
<el-button v-permission type="primary" plain :loading="approving" @click="approvePairing">
|
||||
{{ t('aiTools.agents.approvePairing') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-form ref="formRef" v-loading="deleting" :model="form" :rules="rules" label-position="top">
|
||||
<el-form-item v-if="configured">
|
||||
<el-button type="danger" plain :loading="deleting" @click="deleteChannel">
|
||||
<el-button v-permission type="danger" plain :loading="deleting" @click="deleteChannel">
|
||||
{{ t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -24,7 +24,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="save">
|
||||
<el-button v-permission type="primary" :loading="saving" @click="save">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -34,7 +34,7 @@
|
||||
<el-input v-model="pairingCode" :placeholder="t('aiTools.agents.pairingCodePlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain :loading="approving" @click="approvePairing">
|
||||
<el-button v-permission type="primary" plain :loading="approving" @click="approvePairing">
|
||||
{{ t('aiTools.agents.approvePairing') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-form ref="formRef" v-loading="deleting" :model="form" :rules="rules" label-position="top">
|
||||
<el-form-item v-if="configured">
|
||||
<el-button type="danger" plain :loading="deleting" @click="deleteChannel">
|
||||
<el-button v-permission type="danger" plain :loading="deleting" @click="deleteChannel">
|
||||
{{ t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -25,7 +25,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="save">
|
||||
<el-button v-permission type="primary" :loading="saving" @click="save">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -35,7 +35,7 @@
|
||||
<el-input v-model="pairingCode" :placeholder="t('aiTools.agents.pairingCodePlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain :loading="approving" @click="approvePairing">
|
||||
<el-button v-permission type="primary" plain :loading="approving" @click="approvePairing">
|
||||
{{ t('aiTools.agents.approvePairing') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-form ref="formRef" v-loading="deleting" :model="form" :rules="rules" label-position="top">
|
||||
<el-form-item v-if="configured">
|
||||
<el-button type="danger" plain :loading="deleting" @click="deleteChannel">
|
||||
<el-button v-permission type="danger" plain :loading="deleting" @click="deleteChannel">
|
||||
{{ t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -18,7 +18,7 @@
|
||||
<el-switch v-model="form.requireMention" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="save">
|
||||
<el-button v-permission type="primary" :loading="saving" @click="save">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -28,7 +28,7 @@
|
||||
<el-input v-model="pairingCode" :placeholder="t('aiTools.agents.pairingCodePlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain :loading="approving" @click="approvePairing">
|
||||
<el-button v-permission type="primary" plain :loading="approving" @click="approvePairing">
|
||||
{{ t('aiTools.agents.approvePairing') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-form ref="formRef" v-loading="deleting" :model="form" :rules="rules" label-position="top">
|
||||
<el-form-item v-if="configured">
|
||||
<el-button type="danger" plain :loading="deleting" @click="deleteChannel">
|
||||
<el-button v-permission type="danger" plain :loading="deleting" @click="deleteChannel">
|
||||
{{ t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -25,7 +25,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="save">
|
||||
<el-button v-permission type="primary" :loading="saving" @click="save">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -35,7 +35,7 @@
|
||||
<el-input v-model="pairingCode" :placeholder="t('aiTools.agents.pairingCodePlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain :loading="approving" @click="approvePairing">
|
||||
<el-button v-permission type="primary" plain :loading="approving" @click="approvePairing">
|
||||
{{ t('aiTools.agents.approvePairing') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<el-form v-loading="deleting" label-position="top">
|
||||
<el-form-item v-if="configured">
|
||||
<el-button type="danger" plain :loading="deleting" @click="deleteChannel">
|
||||
<el-button v-permission type="danger" plain :loading="deleting" @click="deleteChannel">
|
||||
{{ t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="loggingIn" @click="loginChannel">
|
||||
<el-button v-permission type="primary" :loading="loggingIn" @click="loginChannel">
|
||||
{{ t('aiTools.agents.scanConnect') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
@save="saveChannel"
|
||||
/>
|
||||
<el-form-item class="mt-4">
|
||||
<el-button type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
<el-button v-permission type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
@approve="approvePairing"
|
||||
/>
|
||||
<el-form-item class="mt-4">
|
||||
<el-button type="primary" :loading="saving" @click="saveChannel">
|
||||
<el-button v-permission type="primary" :loading="saving" @click="saveChannel">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -44,6 +44,7 @@
|
||||
import { reactive, ref } from 'vue';
|
||||
import type { FormInstance } from 'element-plus';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { useMenuManagePermission } from '@/composables/useMenuManagePermission';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { approveAgentChannelPairing, getAgentDiscordConfig, updateAgentDiscordConfig } from '@/api/modules/ai';
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
@approve="approvePairing"
|
||||
/>
|
||||
<el-form-item class="mt-4">
|
||||
<el-button type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
<el-button v-permission type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
@save="saveChannel"
|
||||
/>
|
||||
<el-form-item class="mt-4">
|
||||
<el-button type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
<el-button v-permission type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
@approve="approvePairing"
|
||||
/>
|
||||
<el-form-item class="mt-4">
|
||||
<el-button type="primary" :loading="saving" @click="saveChannel">
|
||||
<el-button v-permission type="primary" :loading="saving" @click="saveChannel">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -78,6 +78,7 @@
|
||||
import { reactive, ref } from 'vue';
|
||||
import type { FormInstance } from 'element-plus';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { useMenuManagePermission } from '@/composables/useMenuManagePermission';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { approveAgentChannelPairing, getAgentTelegramConfig, updateAgentTelegramConfig } from '@/api/modules/ai';
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<el-input v-model="form.secret" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
<el-button v-permission type="primary" :loading="saving" :disabled="!installed" @click="saveChannel">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -77,7 +77,13 @@
|
||||
<el-input v-model="pairingCode" :placeholder="t('aiTools.agents.pairingCodePlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="approving" :disabled="!installed" @click="approvePairing">
|
||||
<el-button
|
||||
v-permission
|
||||
type="primary"
|
||||
:loading="approving"
|
||||
:disabled="!installed"
|
||||
@click="approvePairing"
|
||||
>
|
||||
{{ t('aiTools.agents.approvePairing') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
@@ -16,7 +16,13 @@
|
||||
/>
|
||||
<template v-if="installed">
|
||||
<el-form-item class="mt-4">
|
||||
<el-button type="primary" :loading="loggingIn" :disabled="!installed" @click="loginChannel">
|
||||
<el-button
|
||||
v-permission
|
||||
type="primary"
|
||||
:loading="loggingIn"
|
||||
:disabled="!installed"
|
||||
@click="loginChannel"
|
||||
>
|
||||
{{ t('aiTools.agents.scanConnect') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button type="primary" plain :disabled="!fallbackCandidate" @click="addFallback">
|
||||
<el-button v-permission type="primary" plain :disabled="!fallbackCandidate" @click="addFallback">
|
||||
{{ t('aiTools.agents.addFallbackModel') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -46,7 +46,7 @@
|
||||
:disabled="$index === fallbackRows.length - 1"
|
||||
@click="moveFallback($index, 1)"
|
||||
/>
|
||||
<el-button link :icon="Delete" @click="removeFallback($index)" />
|
||||
<el-button v-permission link :icon="Delete" @click="removeFallback($index)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
@@ -56,7 +56,7 @@
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="saveModel">
|
||||
<el-button v-permission type="primary" :loading="saving" @click="saveModel">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
:placeholder="t('commons.msg.noneData')"
|
||||
/>
|
||||
<div class="mt-4">
|
||||
<el-button type="primary" :loading="saving" @click="confirmSave">
|
||||
<el-button v-permission type="primary" :loading="saving" @click="confirmSave">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<el-input v-model="form.userTimezone" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="saveConfig">
|
||||
<el-button v-permission type="primary" :loading="saving" @click="saveConfig">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="saveConfig">
|
||||
<el-button v-permission type="primary" :loading="saving" @click="saveConfig">
|
||||
{{ t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
<div class="skill-head">
|
||||
<div class="skill-name">{{ skill.name }}</div>
|
||||
<el-button
|
||||
v-permission
|
||||
v-if="skill.uninstallable"
|
||||
link
|
||||
type="danger"
|
||||
@@ -97,6 +98,7 @@
|
||||
<div class="skill-slug">{{ skill.identifier || skill.slug }}</div>
|
||||
</div>
|
||||
<el-button
|
||||
v-permission
|
||||
type="primary"
|
||||
link
|
||||
:loading="installingSkill === (skill.identifier || skill.slug)"
|
||||
@@ -387,6 +389,7 @@ defineExpose({
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-clamp: 2;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
<div class="skill-head">
|
||||
<div class="skill-name">{{ skill.name }}</div>
|
||||
<el-switch
|
||||
v-permission
|
||||
:model-value="!skill.disabled"
|
||||
:loading="updatingSkill === skill.name"
|
||||
@change="(value) => toggleSkill(skill, Boolean(value))"
|
||||
@@ -99,6 +100,7 @@
|
||||
<div class="skill-slug">{{ skill.slug }}</div>
|
||||
</div>
|
||||
<el-button
|
||||
v-permission
|
||||
type="primary"
|
||||
link
|
||||
:loading="installingSkill === skill.slug"
|
||||
@@ -430,6 +432,7 @@ defineExpose({
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-clamp: 2;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
color: var(--el-text-color-secondary);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit" :loading="loading">
|
||||
<el-button v-permission type="primary" @click="submit" :loading="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<DockerStatus v-model:isActive="isActive" v-model:isExist="isExist" />
|
||||
<LayoutContent v-loading="loading" v-if="isExist" :class="{ mask: !isActive }">
|
||||
<template #leftToolBar>
|
||||
<el-button type="primary" @click="openCreate" :disabled="noApp">
|
||||
<el-button v-permission type="primary" @click="openCreate" :disabled="noApp">
|
||||
{{ $t('commons.button.create') }}
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -36,27 +36,30 @@
|
||||
<el-table-column :label="$t('commons.table.status')" prop="status" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-dropdown placement="bottom">
|
||||
<Status :status="row.status" :operate="true" />
|
||||
<Status v-permission :status="row.status" :operate="true" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
<fu-dropdown-item
|
||||
v-permission
|
||||
:disabled="checkStatus('start', row)"
|
||||
@click="onOperate(row, 'start')"
|
||||
>
|
||||
{{ $t('commons.operate.start') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
</fu-dropdown-item>
|
||||
<fu-dropdown-item
|
||||
v-permission
|
||||
:disabled="checkStatus('stop', row)"
|
||||
@click="onOperate(row, 'stop')"
|
||||
>
|
||||
{{ $t('commons.operate.stop') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
</fu-dropdown-item>
|
||||
<fu-dropdown-item
|
||||
v-permission
|
||||
:disabled="checkStatus('restart', row)"
|
||||
@click="onOperate(row, 'restart')"
|
||||
>
|
||||
{{ $t('commons.button.restart') }}
|
||||
</el-dropdown-item>
|
||||
</fu-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
@@ -66,7 +69,13 @@
|
||||
<template #default="{ row }">
|
||||
<div class="version-cell">
|
||||
<span>{{ row.appVersion }}</span>
|
||||
<el-button v-if="row.upgradable" link type="primary" @click="openUpgrade(row)">
|
||||
<el-button
|
||||
v-permission
|
||||
v-if="row.upgradable"
|
||||
link
|
||||
type="primary"
|
||||
@click="openUpgrade(row)"
|
||||
>
|
||||
{{ $t('commons.button.upgrade') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -134,19 +143,20 @@
|
||||
link
|
||||
type="primary"
|
||||
class="website-link-cell__unbind"
|
||||
v-permission
|
||||
@click="onUnbindWebsite(row)"
|
||||
>
|
||||
{{ $t('commons.button.unbind') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-button v-else link type="primary" @click="openBindWebsite(row)">
|
||||
<el-button v-else link type="primary" v-permission @click="openBindWebsite(row)">
|
||||
{{ $t('commons.button.bind') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('website.remark')" prop="remark" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<fu-read-write-switch>
|
||||
<fu-read-write-switch v-permission>
|
||||
<template #read>
|
||||
<MsgInfo :info="row.remark" :width="'150'" />
|
||||
</template>
|
||||
@@ -158,7 +168,12 @@
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('runtime.workDir')" min-width="90">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="openWorkDir(row)">
|
||||
<el-button
|
||||
v-permission:view="'host_file_view'"
|
||||
type="primary"
|
||||
link
|
||||
@click="openWorkDir(row)"
|
||||
>
|
||||
<el-icon>
|
||||
<FolderOpened />
|
||||
</el-icon>
|
||||
@@ -169,7 +184,7 @@
|
||||
<template #default="{ row }">
|
||||
<el-space v-if="supportsAgentToken(row.agentType)">
|
||||
<CopyButton :content="row.token" />
|
||||
<el-button link type="primary" @click="onResetToken(row)">
|
||||
<el-button v-permission link type="primary" @click="onResetToken(row)">
|
||||
{{ $t('commons.button.reset') }}
|
||||
</el-button>
|
||||
</el-space>
|
||||
@@ -247,6 +262,7 @@ import NoApp from '@/views/app-store/apps/no-app/index.vue';
|
||||
import openclawIcon from '@/assets/images/ai-agent-openclaw.svg';
|
||||
import copawIcon from '@/assets/images/ai-agent-copaw.svg';
|
||||
import hermesIcon from '@/assets/images/ai-agent-hermes-agent.svg';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const items = ref<AI.AgentItem[]>([]);
|
||||
const loading = ref(false);
|
||||
@@ -271,6 +287,7 @@ const searchName = ref('');
|
||||
const defaultHttpsPort = ref(443);
|
||||
const openrestyPortLoaded = ref(false);
|
||||
const websiteDomainsMap = ref<Record<number, Website.Domain[]>>({});
|
||||
const { isAdminOrNodeAdmin } = useGlobalStore();
|
||||
|
||||
const headerButtons = [
|
||||
{
|
||||
@@ -289,6 +306,7 @@ const buttons = [
|
||||
{
|
||||
label: i18n.global.t('aiTools.agents.hermesChatAction'),
|
||||
click: (row: AI.AgentItem) => openHermesChat(row),
|
||||
disabled: () => !isAdminOrNodeAdmin.value,
|
||||
show: (row: AI.AgentItem) => row.agentType === 'hermes-agent' && row.status === 'Running',
|
||||
},
|
||||
{
|
||||
@@ -298,6 +316,7 @@ const buttons = [
|
||||
{
|
||||
label: i18n.global.t('menu.terminal'),
|
||||
click: (row: AI.AgentItem) => openTerminal(row),
|
||||
disabled: () => !isAdminOrNodeAdmin.value,
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('menu.home'),
|
||||
@@ -306,25 +325,30 @@ const buttons = [
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.operate.start'),
|
||||
permission: true,
|
||||
click: (row: AI.AgentItem) => onOperate(row, 'start'),
|
||||
disabled: (row: AI.AgentItem) => row.status === 'Running',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.operate.stop'),
|
||||
permission: true,
|
||||
click: (row: AI.AgentItem) => onOperate(row, 'stop'),
|
||||
disabled: (row: AI.AgentItem) => row.status !== 'Running',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.operate.restart'),
|
||||
permission: true,
|
||||
click: (row: AI.AgentItem) => onOperate(row, 'restart'),
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.upgrade'),
|
||||
permission: true,
|
||||
click: (row: AI.AgentItem) => openUpgrade(row),
|
||||
disabled: (row: AI.AgentItem) => !row.upgradable,
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
permission: true,
|
||||
click: (row: AI.AgentItem) => onDelete(row),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<el-button @click="open = false" :disabled="loading">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submit" :disabled="loading">
|
||||
<el-button v-permission type="primary" @click="submit" :disabled="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button :disabled="loading" @click="open = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button :disabled="loading" type="primary" @click="submit">
|
||||
<el-button v-permission :disabled="loading" type="primary" @click="submit">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<div>
|
||||
<LayoutContent>
|
||||
<template #leftToolBar>
|
||||
<el-button type="primary" @click="openCreate">{{ $t('commons.button.create') }}</el-button>
|
||||
<el-button v-permission type="primary" @click="openCreate">
|
||||
{{ $t('commons.button.create') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #rightToolBar>
|
||||
<TableSearch v-model:searchName="searchName" @search="search" />
|
||||
@@ -68,6 +70,7 @@ const searchName = ref('');
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
permission: true,
|
||||
click: (row: AI.AgentAccountItem) => onEdit(row),
|
||||
},
|
||||
{
|
||||
@@ -76,6 +79,7 @@ const buttons = [
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
permission: true,
|
||||
click: (row: AI.AgentAccountItem) => onDelete(row),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<template #content>
|
||||
<div v-loading="loading">
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" @click="openCreate">
|
||||
<el-button v-permission type="primary" @click="openCreate">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -62,7 +62,7 @@
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button :disabled="saving" @click="editorOpen = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button :disabled="saving" type="primary" @click="submit">
|
||||
<el-button v-permission :disabled="saving" type="primary" @click="submit">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
@@ -131,10 +131,12 @@ const rules = reactive({
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
permission: true,
|
||||
click: (row: AI.AgentAccountModel) => openEdit(row),
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
permission: true,
|
||||
click: (row: AI.AgentAccountModel) => onDelete(row),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<el-button @click="handleClose">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="onSubmit(formRef)">
|
||||
<el-button v-permission type="primary" @click="onSubmit(formRef)">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<el-button @click="onCancel">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="onConfirm">
|
||||
<el-button v-permission type="primary" @click="onConfirm">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
<LayoutContent :title="'Servers'" v-loading="loading">
|
||||
<template #leftToolBar>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" @click="openCreate">
|
||||
<el-button v-permission type="primary" @click="openCreate">
|
||||
{{ $t('commons.button.create') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="openDomain">
|
||||
<el-button v-permission type="primary" plain @click="openDomain">
|
||||
{{ $t('aiTools.mcp.bindDomain') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -136,18 +136,21 @@ const getUrl = (row: AI.McpServer) => {
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('menu.config'),
|
||||
permission: true,
|
||||
click: (row: AI.McpServer) => {
|
||||
openConfig(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
permission: true,
|
||||
click: (row: AI.McpServer) => {
|
||||
openDetail(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.start'),
|
||||
permission: true,
|
||||
click: (row: AI.McpServer) => {
|
||||
opServer(row, 'start');
|
||||
},
|
||||
@@ -157,6 +160,7 @@ const buttons = [
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.stop'),
|
||||
permission: true,
|
||||
click: (row: AI.McpServer) => {
|
||||
opServer(row, 'stop');
|
||||
},
|
||||
@@ -166,12 +170,14 @@ const buttons = [
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.restart'),
|
||||
permission: true,
|
||||
click: (row: AI.McpServer) => {
|
||||
opServer(row, 'restart');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
permission: true,
|
||||
click: (row: AI.McpServer) => {
|
||||
deleteServer(row);
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-form-item>
|
||||
<el-button @click="importRef.acceptParams()" type="primary" plain>
|
||||
<el-button v-permission @click="importRef.acceptParams()" type="primary" plain>
|
||||
{{ $t('aiTools.mcp.importMcpJson') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -60,7 +60,7 @@
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="removeEnv(index)" link class="mt-1">
|
||||
<el-button v-permission type="primary" @click="removeEnv(index)" link class="mt-1">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -68,7 +68,9 @@
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="4">
|
||||
<el-button class="mb-2" @click="addEnv">{{ $t('commons.button.add') }}</el-button>
|
||||
<el-button v-permission class="mb-2" @click="addEnv">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
@@ -128,7 +130,7 @@
|
||||
<template #footer>
|
||||
<span>
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit(mcpServerForm)" :disabled="loading">
|
||||
<el-button v-permission type="primary" @click="submit(mcpServerForm)" :disabled="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="removeEnv(index)" link class="mt-1">
|
||||
<el-button v-permission type="primary" @click="removeEnv(index)" link class="mt-1">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -23,7 +23,9 @@
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="4">
|
||||
<el-button @click="addEnv">{{ $t('commons.button.add') }}</el-button>
|
||||
<el-button v-permission @click="addEnv">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<el-button @click="drawerVisible = false">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="onSubmit(formRef)">
|
||||
<el-button v-permission type="primary" @click="onSubmit(formRef)">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</span>
|
||||
|
||||
@@ -70,8 +70,8 @@ import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { getAgentSettingInfo } from '@/api/modules/setting';
|
||||
import { getBindDomain } from '@/api/modules/ai';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { currentNode } = useGlobalStore();
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -106,8 +106,8 @@ const handleClose = () => {
|
||||
};
|
||||
|
||||
const loadSystemIP = async () => {
|
||||
if (globalStore.currentNode !== 'local') {
|
||||
form.systemIP = globalStore.currentNode || i18n.global.t('database.localIP');
|
||||
if (currentNode.value !== 'local') {
|
||||
form.systemIP = currentNode.value || i18n.global.t('database.localIP');
|
||||
return;
|
||||
}
|
||||
const res = await getAgentSettingInfo();
|
||||
|
||||
@@ -25,7 +25,12 @@
|
||||
<el-button @click="handleClose()" :disabled="loading">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="onConfirm" :disabled="loading || checkedItems.length === 0">
|
||||
<el-button
|
||||
v-permission
|
||||
type="primary"
|
||||
@click="onConfirm"
|
||||
:disabled="loading || checkedItems.length === 0"
|
||||
>
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user