Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions frontend/src/api/modules/setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,6 @@ export const listAppNodes = () => {
return http.get<Array<Setting.NodeAppItem>>(`/core/xpack/nodes/apps/update`, {}, { timeout: TimeoutEnum.T_60S });
};

// enterprise
export const loadNodeByUser = () => {
return http.get<Array<Setting.NodeItem>>(`/core/enterprise/users/nodes`);
};
export const uploadEnterpriseLicense = (params: FormData) => {
return http.upload('/core/enterprise/licenses/upload', params);
};
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/complex-table/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,11 @@ const closeRightClick = () => {
const disabled = computed(() => {
return function (btn: any) {
let permissionDisabled = false;
const permissionOptions = { nodeAdmin: btn.nodeAdmin === true };
if (btn.permission === true) {
permissionDisabled = !hasManagePermissionAccess();
permissionDisabled = !hasManagePermissionAccess(undefined, permissionOptions);
} else if (btn.permission !== undefined) {
permissionDisabled = !hasPermissionAccess(btn.permission);
permissionDisabled = !hasPermissionAccess(btn.permission, permissionOptions);
}
const buttonDisabled =
typeof btn.disabled === 'function' ? btn.disabled(rightClick.value.currentRow) : btn.disabled;
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/fu/FuTableOperations.vue
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,11 @@ const getMoreButtons = (row: any) => {

const isButtonDisabled = (button: FuTableOperationButton, row: any) => {
let permissionDisabled = false;
const permissionOptions = { nodeAdmin: button.nodeAdmin === true };
if (button.permission === true) {
permissionDisabled = !hasManagePermissionAccess();
permissionDisabled = !hasManagePermissionAccess(undefined, permissionOptions);
} else if (button.permission !== undefined) {
permissionDisabled = !hasPermissionAccess(button.permission);
permissionDisabled = !hasPermissionAccess(button.permission, permissionOptions);
}
return permissionDisabled || Boolean(resolveMaybeFn(button.disabled ?? false, row));
};
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/fu/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface FuTableOperationButton {
click?: (row: any) => void;
disabled?: boolean | ((row: any) => boolean);
permission?: true | PermissionBindingValue;
nodeAdmin?: boolean;
show?: boolean | ((row: any) => boolean);
type?: string;
icon?: any;
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/directives/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { App, Directive } from 'vue';
import integerInput from './modules/integer';
import permission from './modules/permission';
import permission, { nodeAdminDirective } from './modules/permission';

const directivesList: { [key: string]: Directive } = {
'integer-input': integerInput,
'node-admin': nodeAdminDirective,
permission,
};

Expand Down
22 changes: 20 additions & 2 deletions frontend/src/directives/modules/permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ 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 NODE_ADMIN_PERMISSION_ATTR = 'data-node-admin-permission';

const getDisableTargets = (el: HTMLElement) => {
const targets = [el, ...Array.from(el.querySelectorAll<HTMLElement>('button, input, select, textarea'))];
Expand Down Expand Up @@ -102,11 +103,16 @@ const getPermissionMode = (binding: DirectiveBinding<PermissionBindingValue>): P
return binding.arg === 'view' ? 'view' : 'manage';
};

const hasNodeAdminDirective = (el: HTMLElement, vnode: VNode) => {
return el.hasAttribute(NODE_ADMIN_PERMISSION_ATTR) || !!vnode.dirs?.some((item) => item.dir === nodeAdminDirective);
};

const applyPermission = (el: HTMLElement, binding: DirectiveBinding<PermissionBindingValue>, vnode: VNode) => {
const options = { nodeAdmin: hasNodeAdminDirective(el, vnode) };
const disabled =
getPermissionMode(binding) === 'view'
? !hasPermissionAccess(binding.value)
: !hasManagePermissionAccess(binding.value);
? !hasPermissionAccess(binding.value, options)
: !hasManagePermissionAccess(binding.value, options);
const controller = getComponentPermissionController(vnode);

if (controller?.setPermissionDisabled) {
Expand All @@ -130,4 +136,16 @@ const permissionDirective: Directive<HTMLElement, PermissionBindingValue> = {
},
};

export const nodeAdminDirective: Directive<HTMLElement, boolean | undefined> = {
mounted(el) {
el.setAttribute(NODE_ADMIN_PERMISSION_ATTR, 'true');
},
updated(el) {
el.setAttribute(NODE_ADMIN_PERMISSION_ATTR, 'true');
},
unmounted(el) {
el.removeAttribute(NODE_ADMIN_PERMISSION_ATTR);
},
};

export default permissionDirective;
12 changes: 3 additions & 9 deletions frontend/src/utils/node.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Setting } from '@/api/interface/setting';
import { listNodeOptions, loadNodeByUser } from '@/api/modules/setting';
import { listNodeOptions } from '@/api/modules/setting';
import { GlobalStore } from '@/store';

export const changeToLocal = async () => {
Expand All @@ -23,15 +23,9 @@ export const changeToLocal = async () => {
};

export async function listNodes(type: string): Promise<Array<Setting.NodeItem>> {
const globalStore = GlobalStore();
try {
if (globalStore.isAdmin) {
const res = await listNodeOptions(type);
return res.data || [];
} else {
const res = await loadNodeByUser();
return res.data || [];
}
const res = await listNodeOptions(type);
return res.data || [];
Comment on lines +27 to +28

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve per-user node filtering

For enterprise non-admin users, this now calls the all-node /core/nodes/list endpoint instead of the previous /core/enterprise/users/nodes path, so the sidebar/node selectors that consume listNodes('all') can be populated with nodes outside the user's assigned node roles. In particular, Collapse.vue trusts this list when switching nodes, so a node admin with access to only one node can be shown and attempt to switch to every healthy bound node returned by the all-node API; keep using the user-scoped endpoint for non-admins or ensure this wrapper remains user-scoped.

Useful? React with 👍 / 👎.

} catch (error) {
return [];
}
Expand Down
20 changes: 15 additions & 5 deletions frontend/src/utils/permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { normalizeToManageCode } from '@/utils/permission-codes';

export type PermissionBindingValue = string | string[] | undefined;
export type PermissionMode = 'manage' | 'view';
export type PermissionAccessOptions = {
nodeAdmin?: boolean;
};

const getRoutePermission = (): PermissionBindingValue => {
const route = router.currentRoute.value;
Expand Down Expand Up @@ -46,8 +49,15 @@ export const toPermissionList = (value: PermissionBindingValue) => {
return routePermission ? [routePermission] : [];
};

const hasPermissionAccessByMode = (mode: PermissionMode, value?: PermissionBindingValue) => {
const hasPermissionAccessByMode = (
mode: PermissionMode,
value?: PermissionBindingValue,
options: PermissionAccessOptions = {},
) => {
const globalStore = GlobalStore();
if (options.nodeAdmin && globalStore.isNodeAdmin) {
return true;
}
const permissions = toPermissionList(value);
const normalizedPermissions = mode === 'manage' ? permissions.map(toManagePermission).filter(Boolean) : permissions;
if (normalizedPermissions.length === 0) {
Expand All @@ -56,10 +66,10 @@ const hasPermissionAccessByMode = (mode: PermissionMode, value?: PermissionBindi
return normalizedPermissions.some((permission) => globalStore.hasPermission(permission));
};

export const hasManagePermissionAccess = (value?: PermissionBindingValue) => {
return hasPermissionAccessByMode('manage', value);
export const hasManagePermissionAccess = (value?: PermissionBindingValue, options?: PermissionAccessOptions) => {
return hasPermissionAccessByMode('manage', value, options);
};

export const hasPermissionAccess = (value?: PermissionBindingValue) => {
return hasPermissionAccessByMode('view', value);
export const hasPermissionAccess = (value?: PermissionBindingValue, options?: PermissionAccessOptions) => {
return hasPermissionAccessByMode('view', value, options);
};
25 changes: 19 additions & 6 deletions frontend/src/views/cronjob/cronjob/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
<div>
<LayoutContent v-loading="loading" v-if="!isRecordShow" :title="$t('menu.cronjob')">
<template #leftToolBar>
<el-button v-permission type="primary" @click="onOpenDialog('')">
<el-button v-permission v-node-admin type="primary" @click="onOpenDialog('')">
{{ $t('commons.button.create') }}
</el-button>
<el-button v-permission @click="onOpenGroupDialog()">
<el-button v-permission v-node-admin @click="onOpenGroupDialog()">
{{ $t('commons.table.group') }}
</el-button>
<el-button-group>
<el-button
v-permission
v-node-admin
plain
:disabled="selects.length === 0"
@click="onBatchChangeStatus('enable')"
Expand All @@ -19,22 +20,23 @@
</el-button>
<el-button
v-permission
v-node-admin
plain
:disabled="selects.length === 0"
@click="onBatchChangeStatus('disable')"
>
{{ $t('commons.button.disable') }}
</el-button>
<el-button v-permission plain :disabled="selects.length === 0" @click="onDelete(null)">
<el-button v-permission v-node-admin plain :disabled="selects.length === 0" @click="onDelete(null)">
{{ $t('commons.button.delete') }}
</el-button>
</el-button-group>

<el-button-group>
<el-button v-permission @click="onImport">
<el-button v-permission v-node-admin @click="onImport">
{{ $t('commons.button.import') }}
</el-button>
<el-button v-permission :disabled="selects.length === 0" @click="onExport">
<el-button v-permission v-node-admin :disabled="selects.length === 0" @click="onExport">
{{ $t('commons.button.export') }}
</el-button>
</el-button-group>
Expand Down Expand Up @@ -80,7 +82,12 @@
</el-table-column>
<el-table-column :label="$t('commons.table.group')" min-width="120" prop="group">
<template #default="{ row }">
<fu-select-rw-switch v-permission v-model="row.groupID" @change="updateGroup(row)">
<fu-select-rw-switch
v-permission
v-node-admin
v-model="row.groupID"
@change="updateGroup(row)"
>
<template #read>
{{ row.groupBelong === 'Default' ? $t('commons.table.default') : row.groupBelong }}
</template>
Expand All @@ -99,13 +106,15 @@
<template #default="{ row }">
<Status
v-permission
v-node-admin
v-if="row.status === 'Enable'"
@click="onChangeStatus(row.id, 'disable')"
:status="row.status"
:operate="true"
/>
<Status
v-permission
v-node-admin
v-if="row.status === 'Disable'"
@click="onChangeStatus(row.id, 'enable')"
:status="row.status"
Expand Down Expand Up @@ -139,6 +148,7 @@
<template #default="{ row }">
<el-button
v-permission
v-node-admin
v-if="hasBackup(row.type)"
@click="loadBackups(row)"
plain
Expand Down Expand Up @@ -511,6 +521,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.handle'),
permission: true,
nodeAdmin: true,
click: (row: Cronjob.CronjobInfo) => {
onHandle(row);
},
Expand All @@ -527,13 +538,15 @@ const buttons = [
{
label: i18n.global.t('commons.button.edit'),
permission: true,
nodeAdmin: true,
click: (row: Cronjob.CronjobInfo) => {
onOpenDialog(row.id + '');
},
},
{
label: i18n.global.t('commons.button.delete'),
permission: true,
nodeAdmin: true,
click: (row: Cronjob.CronjobInfo) => {
onDelete(row);
},
Expand Down
12 changes: 11 additions & 1 deletion frontend/src/views/cronjob/cronjob/record/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<el-button
type="primary"
v-permission
v-node-admin
:disabled="dialogData.rowData.status === 'Pending'"
@click="onHandle(dialogData.rowData)"
link
Expand All @@ -44,6 +45,7 @@
type="primary"
v-if="dialogData.rowData.status === 'Enable'"
v-permission
v-node-admin
@click="onChangeStatus(dialogData.rowData.id, 'disable')"
link
>
Expand All @@ -53,13 +55,21 @@
type="primary"
v-if="dialogData.rowData.status === 'Disable'"
v-permission
v-node-admin
@click="onChangeStatus(dialogData.rowData.id, 'enable')"
link
>
{{ $t('commons.button.enable') }}
</el-button>
<el-divider direction="vertical" />
<el-button v-permission :disabled="!hasRecords" type="primary" @click="onClean" link>
<el-button
v-permission
v-node-admin
:disabled="!hasRecords"
type="primary"
@click="onClean"
link
>
{{ $t('commons.button.clean') }}
</el-button>
</div>
Expand Down
25 changes: 19 additions & 6 deletions frontend/src/views/cronjob/library/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div>
<LayoutContent v-loading="loading" :title="$t('logs.login')">
<template #leftToolBar>
<el-button v-permission type="primary" @click="onOpenDialog('create')">
<el-button v-permission v-node-admin type="primary" @click="onOpenDialog('create')">
{{ $t('commons.button.create') }}
</el-button>
<el-dropdown @command="handleSyncOp" class="mr-2.5">
Expand All @@ -12,23 +12,33 @@
</el-button>
<template #dropdown>
<el-dropdown-menu>
<fu-dropdown-item v-permission command="sync">
<fu-dropdown-item v-permission v-node-admin command="sync">
{{ $t('cronjob.library.syncNow') }}
</fu-dropdown-item>
<fu-dropdown-item v-if="scriptSync === 'Disable'" v-permission command="turnOnSync">
<fu-dropdown-item
v-if="scriptSync === 'Disable'"
v-permission
v-node-admin
command="turnOnSync"
>
{{ $t('cronjob.library.turnOnSync') }}
</fu-dropdown-item>
<fu-dropdown-item v-if="scriptSync === 'Enable'" v-permission command="turnOffSync">
<fu-dropdown-item
v-if="scriptSync === 'Enable'"
v-permission
v-node-admin
command="turnOffSync"
>
{{ $t('cronjob.library.turnOffSync') }}
</fu-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>

<el-button v-permission type="primary" plain @click="onOpenGroupDialog()">
<el-button v-permission v-node-admin type="primary" plain @click="onOpenGroupDialog()">
{{ $t('commons.table.group') }}
</el-button>
<el-button v-permission plain :disabled="selects.length === 0" @click="onDelete(null)">
<el-button v-permission v-node-admin plain :disabled="selects.length === 0" @click="onDelete(null)">
{{ $t('commons.button.delete') }}
</el-button>
</template>
Expand Down Expand Up @@ -326,6 +336,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.clone'),
permission: true,
nodeAdmin: true,
disabled: (row: any) => {
return !row.isSystem;
},
Expand All @@ -341,6 +352,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.edit'),
permission: true,
nodeAdmin: true,
disabled: (row: any) => {
return row.isSystem;
},
Expand All @@ -351,6 +363,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.delete'),
permission: true,
nodeAdmin: true,
disabled: (row: any) => {
return row.isSystem;
},
Expand Down
Loading
Loading