diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index 7abba6f42bde..7316b9586ba7 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -6,7 +6,7 @@
diff --git a/frontend/src/components/fu/FuDropdownItem.vue b/frontend/src/components/fu/FuDropdownItem.vue
new file mode 100644
index 000000000000..a7c7cd7a882d
--- /dev/null
+++ b/frontend/src/components/fu/FuDropdownItem.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/fu/FuInputRwSwitch.vue b/frontend/src/components/fu/FuInputRwSwitch.vue
index d33226355ccf..fcdf8e675f08 100644
--- a/frontend/src/components/fu/FuInputRwSwitch.vue
+++ b/frontend/src/components/fu/FuInputRwSwitch.vue
@@ -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,
+});
diff --git a/frontend/src/components/fu/FuReadWriteSwitch.vue b/frontend/src/components/fu/FuReadWriteSwitch.vue
index e10293514e86..b03f90dbe70c 100644
--- a/frontend/src/components/fu/FuReadWriteSwitch.vue
+++ b/frontend/src/components/fu/FuReadWriteSwitch.vue
@@ -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,
});
diff --git a/frontend/src/components/fu/FuSelectRwSwitch.vue b/frontend/src/components/fu/FuSelectRwSwitch.vue
index 8ac5a94e64b0..b7a280df73cb 100644
--- a/frontend/src/components/fu/FuSelectRwSwitch.vue
+++ b/frontend/src/components/fu/FuSelectRwSwitch.vue
@@ -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();
}
};
diff --git a/frontend/src/components/fu/FuTableOperations.vue b/frontend/src/components/fu/FuTableOperations.vue
index 55c9e598edcd..0dbe06f8a883 100644
--- a/frontend/src/components/fu/FuTableOperations.vue
+++ b/frontend/src/components/fu/FuTableOperations.vue
@@ -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) => {
diff --git a/frontend/src/components/fu/index.ts b/frontend/src/components/fu/index.ts
index e2ab9f74c248..0a1ebe0cc974 100644
--- a/frontend/src/components/fu/index.ts
+++ b/frontend/src/components/fu/index.ts
@@ -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,
diff --git a/frontend/src/components/fu/shared.ts b/frontend/src/components/fu/shared.ts
index e612a3065ad1..c063d3ec014f 100644
--- a/frontend/src/components/fu/shared.ts
+++ b/frontend/src/components/fu/shared.ts
@@ -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;
diff --git a/frontend/src/components/license-import/index.vue b/frontend/src/components/license-import/index.vue
index ec9629a05893..7fefabd04b97 100644
--- a/frontend/src/components/license-import/index.vue
+++ b/frontend/src/components/license-import/index.vue
@@ -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']);
@@ -112,7 +112,7 @@ const handleExceed: UploadProps['onExceed'] = (files) => {
};
const toLxware = () => {
- 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');
@@ -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();
diff --git a/frontend/src/components/log/compose/index.vue b/frontend/src/components/log/compose/index.vue
index 0af49a0eb2a3..90ee7cf1845e 100644
--- a/frontend/src/components/log/compose/index.vue
+++ b/frontend/src/components/log/compose/index.vue
@@ -3,7 +3,7 @@
v-model="open"
:header="resource"
@close="handleClose"
- :size="globalStore.isFullScreen ? 'full' : '60%'"
+ :size="isFullScreen ? 'full' : '60%'"
:resource="container"
>
@@ -26,16 +26,14 @@
diff --git a/frontend/src/layout/components/Sidebar/components/Collapse.vue b/frontend/src/layout/components/Sidebar/components/Collapse.vue
index d30a81dc173e..ff17ae7f3a17 100644
--- a/frontend/src/layout/components/Sidebar/components/Collapse.vue
+++ b/frontend/src/layout/components/Sidebar/components/Collapse.vue
@@ -87,12 +87,12 @@
diff --git a/frontend/src/views/log/system/index.vue b/frontend/src/views/log/system/index.vue
index afcec6f81e26..41face13ffe1 100644
--- a/frontend/src/views/log/system/index.vue
+++ b/frontend/src/views/log/system/index.vue
@@ -14,12 +14,7 @@
{{ $t('commons.button.watch') }}
-
+
@@ -44,8 +39,8 @@ import LogFile from '@/components/log/file/index.vue';
import LogRouter from '@/views/log/router/index.vue';
import { nextTick, onMounted, reactive, ref } from 'vue';
import { getSystemFiles } from '@/api/modules/log';
-import { GlobalStore } from '@/store';
-const globalStore = GlobalStore();
+import { useGlobalStore } from '@/composables/useGlobalStore';
+const { currentNode } = useGlobalStore();
const loading = ref();
const isWatch = ref();
diff --git a/frontend/src/views/log/website/index.vue b/frontend/src/views/log/website/index.vue
index d50afa2473f2..d992ef9790da 100644
--- a/frontend/src/views/log/website/index.vue
+++ b/frontend/src/views/log/website/index.vue
@@ -34,10 +34,10 @@
{{ $t('commons.button.watch') }}
-
+
{{ $t('commons.button.download') }}
-
+
{{ $t('logs.deleteLogs') }}
diff --git a/frontend/src/views/login/components/login-form.vue b/frontend/src/views/login/components/login-form.vue
index 5c189d30d48e..4f5cf935320c 100644
--- a/frontend/src/views/login/components/login-form.vue
+++ b/frontend/src/views/login/components/login-form.vue
@@ -52,10 +52,10 @@
- English
+ English
中文(简体)
中文(繁體)
- English
+ English
日本語
Português (Brasil)
한국어
@@ -110,10 +110,10 @@
- English
+ English
中文(简体)
中文(繁體)
- English
+ English
日本語
Português (Brasil)
한국어
@@ -151,7 +151,7 @@
>
-
+
-
+
globalStore.themeConfig);
-const globalStore = GlobalStore();
+const {
+ globalStore,
+ agreeLicense,
+ currentNode,
+ ignoreCaptcha,
+ isAdmin,
+ isEnterprise,
+ isEnterpriseLicenseLoaded,
+ isFxplay,
+ isIntl,
+ isLogin,
+ isOffline,
+ isOnRestart,
+ openMenuTabs,
+ themeConfig,
+} = useGlobalStore();
const menuStore = MenuStore();
const tabsStore = TabsStore();
@@ -273,8 +288,6 @@ const passkeySupported = ref(false);
const autoPasskeyEnabledKey = '1panel-passkey-auto-enabled';
const showPasswordLogin = ref(false);
const isDemo = ref(false);
-const isIntl = ref(true);
-const isFxplay = ref(false);
const open = ref(false);
const loginBtnLinkColor = ref(null);
@@ -425,7 +438,7 @@ const login = (formEl: FormInstance | undefined) => {
authMethod: 'session',
language: loginForm.language,
};
- if (!globalStore.ignoreCaptcha && requestLoginForm.captcha == '') {
+ if (!ignoreCaptcha.value && requestLoginForm.captcha == '') {
errCaptcha.value = true;
return;
}
@@ -433,7 +446,7 @@ const login = (formEl: FormInstance | undefined) => {
isLoggingIn = true;
loading.value = true;
const res = await loginApi(requestLoginForm);
- globalStore.ignoreCaptcha = true;
+ ignoreCaptcha.value = true;
if (res.data.mfaStatus === 'Enable') {
mfaLoginForm.sessionId = res.data.mfaSession || '';
mfaLoginForm.code = '';
@@ -445,13 +458,13 @@ const login = (formEl: FormInstance | undefined) => {
});
return;
}
- globalStore.isLogin = true;
- globalStore.agreeLicense = true;
+ isLogin.value = true;
+ agreeLicense.value = true;
menuStore.setMenuList([]);
tabsStore.removeAllTabs();
- globalStore.isAdmin = res.data.role === 'ADMIN';
+ isAdmin.value = res.data.role === 'ADMIN';
await changeToLocal();
- await syncAuthInfo(globalStore.currentNode);
+ await syncAuthInfo(currentNode.value);
MsgSuccess(i18n.t('commons.msg.loginSuccess'));
localStorage.removeItem('dashboardCache');
localStorage.removeItem('upgradeChecked');
@@ -460,7 +473,7 @@ const login = (formEl: FormInstance | undefined) => {
} catch (res) {
if (res.code === 401) {
if (res.message === 'ErrCaptchaCode') {
- globalStore.ignoreCaptcha = false;
+ ignoreCaptcha.value = false;
loginForm.captcha = '';
errCaptcha.value = true;
errAuthInfo.value = false;
@@ -468,7 +481,7 @@ const login = (formEl: FormInstance | undefined) => {
return;
}
if (res.message === 'ErrAuth') {
- globalStore.ignoreCaptcha = false;
+ ignoreCaptcha.value = false;
errCaptcha.value = false;
errAuthInfo.value = true;
loginVerify();
@@ -491,13 +504,13 @@ const mfaLogin = async (auto: boolean) => {
try {
errMfaInfo.value = false;
const res = await mfaLoginApi(mfaLoginForm);
- globalStore.isLogin = true;
+ isLogin.value = true;
menuStore.setMenuList([]);
tabsStore.removeAllTabs();
MsgSuccess(i18n.t('commons.msg.loginSuccess'));
- globalStore.isAdmin = res.data.role === 'ADMIN';
+ isAdmin.value = res.data.role === 'ADMIN';
await changeToLocal();
- await syncAuthInfo(globalStore.currentNode);
+ await syncAuthInfo(currentNode.value);
localStorage.removeItem('dashboardCache');
localStorage.removeItem('upgradeChecked');
routerToName('home');
@@ -505,7 +518,7 @@ const mfaLogin = async (auto: boolean) => {
} catch (res) {
if (res.code === 401) {
if (res.message === 'ErrCaptchaCode') {
- globalStore.ignoreCaptcha = false;
+ ignoreCaptcha.value = false;
mfaLoginForm.code = '';
mfaShow.value = false;
loginVerify();
@@ -557,14 +570,14 @@ const passkeyLogin = async () => {
const payload = buildPasskeyAssertion(credential);
const loginRes = await passkeyFinishApi(payload, res.data.sessionId);
enableAutoPasskey();
- globalStore.ignoreCaptcha = true;
- globalStore.isLogin = true;
- globalStore.agreeLicense = true;
+ ignoreCaptcha.value = true;
+ isLogin.value = true;
+ agreeLicense.value = true;
menuStore.setMenuList([]);
tabsStore.removeAllTabs();
- globalStore.isAdmin = loginRes.data.role === 'ADMIN';
+ isAdmin.value = loginRes.data.role === 'ADMIN';
await changeToLocal();
- await syncAuthInfo(globalStore.currentNode);
+ await syncAuthInfo(currentNode.value);
MsgSuccess(i18n.t('commons.msg.loginSuccess'));
localStorage.removeItem('dashboardCache');
localStorage.removeItem('upgradeChecked');
@@ -627,20 +640,19 @@ const getSetting = async () => {
await handleCommand(language);
isIntl.value = res.data.isIntl;
isFxplay.value = res.data.isFxplay;
- globalStore.isFxplay = isFxplay.value;
- globalStore.isOffline = res.data.isOffline;
- globalStore.isEnterprise = res.data.isEnterprise;
- globalStore.isEnterpriseLicenseLoaded = !res.data.isEnterprise;
- globalStore.ignoreCaptcha = !res.data.needCaptcha;
+ isOffline.value = res.data.isOffline;
+ isEnterprise.value = res.data.isEnterprise;
+ isEnterpriseLicenseLoaded.value = !res.data.isEnterprise;
+ ignoreCaptcha.value = !res.data.needCaptcha;
passkeySetting.value = res.data.passkeySetting;
- if (!globalStore.ignoreCaptcha) {
+ if (!ignoreCaptcha.value) {
loginVerify();
}
document.title = res.data.panelName;
i18n.warnHtmlMessage = false;
- globalStore.openMenuTabs = res.data.menuTabs === 'Enable';
- globalStore.themeConfig = { ...themeConfig.value, theme: res.data.theme, panelName: res.data.panelName };
+ openMenuTabs.value = res.data.menuTabs === 'Enable';
+ themeConfig.value = { ...themeConfig.value, theme: res.data.theme, panelName: res.data.panelName };
if (res.data.passkeySetting && !isIntl.value && !isFxplay.value) {
loginForm.agreeLicense = true;
@@ -652,15 +664,15 @@ const getSetting = async () => {
};
onMounted(() => {
- globalStore.isOnRestart = false;
+ isOnRestart.value = false;
passkeySupported.value = !!window.PublicKeyCredential && window.isSecureContext;
getSetting();
getXpackSettingForTheme();
- if (!globalStore.ignoreCaptcha) {
+ if (!ignoreCaptcha.value) {
loginVerify();
}
- document.title = globalStore.themeConfig.panelName;
- loginBtnLinkColor.value = globalStore.themeConfig.loginBtnLinkColor || '#005eeb';
+ document.title = themeConfig.value.panelName;
+ loginBtnLinkColor.value = themeConfig.value.loginBtnLinkColor || '#005eeb';
document.documentElement.style.setProperty('--login-btn-link-color', loginBtnLinkColor.value);
document.documentElement.style.setProperty(
'--login-btn-link-hover-color',
@@ -673,7 +685,7 @@ onMounted(() => {
nextTick(() => {
userNameRef.value?.focus();
});
- loginForm.agreeLicense = globalStore.agreeLicense;
+ loginForm.agreeLicense = agreeLicense.value;
document.onkeydown = (e: any) => {
e = window.event || e;
if (e.keyCode === 13) {
diff --git a/frontend/src/views/login/index.vue b/frontend/src/views/login/index.vue
index e86d729fdbbb..5210f7f32237 100644
--- a/frontend/src/views/login/index.vue
+++ b/frontend/src/views/login/index.vue
@@ -28,10 +28,10 @@