mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-05-14 15:01:44 +00:00
fix: some actions should be hidden for read-only user (#855)
* fix: some actions limited for virt-viewer Signed-off-by: Andy Lee <andy.lee@suse.com> * refactor: based on schema collectionMethods Signed-off-by: Andy Lee <andy.lee@suse.com> * fix: conditionally add migrate action to available actions * fix: update collectionMethods check to use find for case-insensitive matching * fix: update canEditClusterMembers method to use schema for collectionMethods * fix: update canCreateImage check to use case-insensitive matching --------- Signed-off-by: Andy Lee <andy.lee@suse.com>
This commit is contained in:
parent
032700293c
commit
5cc8b4c301
@ -122,7 +122,7 @@ export default {
|
|||||||
return this.$store.getters['currentCluster'].isLocal;
|
return this.$store.getters['currentCluster'].isLocal;
|
||||||
},
|
},
|
||||||
canEditClusterMembers() {
|
canEditClusterMembers() {
|
||||||
return this.normanClusterRTBSchema?.collectionMethods.find((x) => x.toLowerCase() === 'post');
|
return this.schema?.collectionMethods.find((x) => x.toLowerCase() === 'post');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -686,7 +686,7 @@ export function init($plugin, store) {
|
|||||||
},
|
},
|
||||||
resource: NETWORK_ATTACHMENT,
|
resource: NETWORK_ATTACHMENT,
|
||||||
resourceDetail: HCI.NETWORK_ATTACHMENT,
|
resourceDetail: HCI.NETWORK_ATTACHMENT,
|
||||||
resourceEdit: HCI.NETWORK_ATTACHMENT
|
resourceEdit: HCI.NETWORK_ATTACHMENT,
|
||||||
});
|
});
|
||||||
|
|
||||||
virtualType({
|
virtualType({
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { Banner } from '@components/Banner';
|
|||||||
import Loading from '@shell/components/Loading';
|
import Loading from '@shell/components/Loading';
|
||||||
import ResourceTable from '@shell/components/ResourceTable';
|
import ResourceTable from '@shell/components/ResourceTable';
|
||||||
import BadgeState from '@shell/components/formatter/BadgeStateFormatter';
|
import BadgeState from '@shell/components/formatter/BadgeStateFormatter';
|
||||||
|
|
||||||
import { NAME, AGE, NAMESPACE, STATE } from '@shell/config/table-headers';
|
import { NAME, AGE, NAMESPACE, STATE } from '@shell/config/table-headers';
|
||||||
import { NETWORK_ATTACHMENT, SCHEMA } from '@shell/config/types';
|
import { NETWORK_ATTACHMENT, SCHEMA } from '@shell/config/types';
|
||||||
import { allHash } from '@shell/utils/promise';
|
import { allHash } from '@shell/utils/promise';
|
||||||
|
|||||||
@ -30,11 +30,12 @@ const STATUS_DISPLAY = {
|
|||||||
export default class PCIDevice extends SteveModel {
|
export default class PCIDevice extends SteveModel {
|
||||||
get _availableActions() {
|
get _availableActions() {
|
||||||
const out = super._availableActions;
|
const out = super._availableActions;
|
||||||
|
const canUpdate = !!this.linkFor('update');
|
||||||
|
|
||||||
out.push(
|
out.push(
|
||||||
{
|
{
|
||||||
action: 'enablePassthroughBulk',
|
action: 'enablePassthroughBulk',
|
||||||
enabled: !this.isEnabling && !this.isvGPUDevice,
|
enabled: !this.isEnabling && !this.isvGPUDevice && canUpdate,
|
||||||
icon: 'icon icon-fw icon-dot',
|
icon: 'icon icon-fw icon-dot',
|
||||||
label: 'Enable Passthrough',
|
label: 'Enable Passthrough',
|
||||||
bulkable: true,
|
bulkable: true,
|
||||||
@ -43,7 +44,7 @@ export default class PCIDevice extends SteveModel {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'disablePassthrough',
|
action: 'disablePassthrough',
|
||||||
enabled: this.isEnabling && this.claimedByMe && !this.isvGPUDevice,
|
enabled: this.isEnabling && this.claimedByMe && !this.isvGPUDevice && canUpdate,
|
||||||
icon: 'icon icon-fw icon-dot-open',
|
icon: 'icon icon-fw icon-dot-open',
|
||||||
label: 'Disable Passthrough',
|
label: 'Disable Passthrough',
|
||||||
bulkable: true,
|
bulkable: true,
|
||||||
|
|||||||
@ -24,7 +24,7 @@ const OBSCURE_NAMESPACE_PREFIX = [
|
|||||||
|
|
||||||
export default class HciNamespace extends namespace {
|
export default class HciNamespace extends namespace {
|
||||||
get _availableActions() {
|
get _availableActions() {
|
||||||
const out = super._availableActions;
|
let out = super._availableActions;
|
||||||
const remove = out.findIndex((a) => a.action === 'promptRemove');
|
const remove = out.findIndex((a) => a.action === 'promptRemove');
|
||||||
|
|
||||||
const promptRemove = {
|
const promptRemove = {
|
||||||
@ -53,6 +53,16 @@ export default class HciNamespace extends namespace {
|
|||||||
insertAt(out, out.length - 1, promptRemove);
|
insertAt(out, out.length - 1, promptRemove);
|
||||||
insertAt(out, out.length - 5, editQuotaAction);
|
insertAt(out, out.length - 5, editQuotaAction);
|
||||||
|
|
||||||
|
const canUpdate = !!this.linkFor('update');
|
||||||
|
|
||||||
|
out = out.map((action) => {
|
||||||
|
if (['move'].includes(action.action)) {
|
||||||
|
return { ...action, enabled: action.enabled && canUpdate };
|
||||||
|
}
|
||||||
|
|
||||||
|
return action;
|
||||||
|
});
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,20 @@ import Secret from '@shell/models/secret';
|
|||||||
import { NAMESPACE } from '@shell/config/types';
|
import { NAMESPACE } from '@shell/config/types';
|
||||||
|
|
||||||
export default class HciSecret extends Secret {
|
export default class HciSecret extends Secret {
|
||||||
|
get _availableActions() {
|
||||||
|
let out = super._availableActions;
|
||||||
|
|
||||||
|
out = out.map((action) => {
|
||||||
|
if (['download'].includes(action.action)) {
|
||||||
|
return { ...action, enabled: !!this.linkFor('update') };
|
||||||
|
}
|
||||||
|
|
||||||
|
return action;
|
||||||
|
});
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
// prevent harvester secret detail page be overridden.
|
// prevent harvester secret detail page be overridden.
|
||||||
// See isFullPageOverride in https://github.com/rancher/dashboard/blob/master/shell/components/ResourceDetail/index.vue
|
// See isFullPageOverride in https://github.com/rancher/dashboard/blob/master/shell/components/ResourceDetail/index.vue
|
||||||
get fullDetailPageOverride() {
|
get fullDetailPageOverride() {
|
||||||
|
|||||||
@ -106,6 +106,15 @@ export default class HciStorageClass extends StorageClass {
|
|||||||
|
|
||||||
get availableActions() {
|
get availableActions() {
|
||||||
let out = super.availableActions || [];
|
let out = super.availableActions || [];
|
||||||
|
const canUpdate = !!this.linkFor('update');
|
||||||
|
|
||||||
|
out = out.map((action) => {
|
||||||
|
if (['setDefault', 'setAsDefault', 'resetDefault'].includes(action.action)) {
|
||||||
|
return { ...action, enabled: canUpdate };
|
||||||
|
}
|
||||||
|
|
||||||
|
return action;
|
||||||
|
});
|
||||||
|
|
||||||
if (this.isInternalStorageClass()) {
|
if (this.isInternalStorageClass()) {
|
||||||
out = out.filter((action) => {
|
out = out.filter((action) => {
|
||||||
|
|||||||
@ -21,9 +21,10 @@ export default class HciAddonConfig extends HarvesterResource {
|
|||||||
out.push(rancherDashboard);
|
out.push(rancherDashboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canUpdate = !!this.linkFor('update');
|
||||||
const toggleAddon = {
|
const toggleAddon = {
|
||||||
action: 'toggleAddon',
|
action: 'toggleAddon',
|
||||||
enabled: true,
|
enabled: canUpdate,
|
||||||
icon: this.spec.enabled ? 'icon icon-pause' : 'icon icon-play',
|
icon: this.spec.enabled ? 'icon icon-pause' : 'icon icon-play',
|
||||||
label: this.spec.enabled ? this.t('generic.disable') : this.t('generic.enable'),
|
label: this.spec.enabled ? this.t('generic.disable') : this.t('generic.enable'),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,20 @@ import { findBy } from '@shell/utils/array';
|
|||||||
import HarvesterResource from './harvester';
|
import HarvesterResource from './harvester';
|
||||||
|
|
||||||
export default class HciKeypair extends HarvesterResource {
|
export default class HciKeypair extends HarvesterResource {
|
||||||
|
get _availableActions() {
|
||||||
|
let out = super._availableActions;
|
||||||
|
|
||||||
|
out = out.map((action) => {
|
||||||
|
if (['download'].includes(action.action)) {
|
||||||
|
return { ...action, enabled: !!this.linkFor('update') };
|
||||||
|
}
|
||||||
|
|
||||||
|
return action;
|
||||||
|
});
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
get stateDisplay() {
|
get stateDisplay() {
|
||||||
const conditions = get(this, 'status.conditions');
|
const conditions = get(this, 'status.conditions');
|
||||||
const status = (findBy(conditions, 'type', 'validated') || {}).status ;
|
const status = (findBy(conditions, 'type', 'validated') || {}).status ;
|
||||||
|
|||||||
@ -19,16 +19,18 @@ export default class ScheduleVmBackup extends HarvesterResource {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const canUpdate = !!this.linkFor('update');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
action: 'resumeSchedule',
|
action: 'resumeSchedule',
|
||||||
enabled: ucFirst(this.state) === STATES.suspended.label,
|
enabled: canUpdate && ucFirst(this.state) === STATES.suspended.label,
|
||||||
icon: 'icons icon-play',
|
icon: 'icons icon-play',
|
||||||
label: this.t('harvester.action.resumeSchedule'),
|
label: this.t('harvester.action.resumeSchedule'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'suspendSchedule',
|
action: 'suspendSchedule',
|
||||||
enabled: ucFirst(this.state) === STATES.active.label,
|
enabled: canUpdate && ucFirst(this.state) === STATES.active.label,
|
||||||
icon: 'icons icon-pause',
|
icon: 'icons icon-pause',
|
||||||
label: this.t('harvester.action.suspendSchedule'),
|
label: this.t('harvester.action.suspendSchedule'),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -52,6 +52,7 @@ export default class HciVmImage extends HarvesterResource {
|
|||||||
canCreateVM = false;
|
canCreateVM = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canCreateImage = !!this.$getters?.['schemaFor']?.(HCI.IMAGE)?.collectionMethods?.some((method) => method.toLowerCase() === 'post');
|
||||||
const customActions = this.isReady ? [
|
const customActions = this.isReady ? [
|
||||||
{
|
{
|
||||||
action: 'createFromImage',
|
action: 'createFromImage',
|
||||||
@ -61,13 +62,13 @@ export default class HciVmImage extends HarvesterResource {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'encryptImage',
|
action: 'encryptImage',
|
||||||
enabled: this.volumeEncryptionFeatureEnabled && !this.isEncrypted,
|
enabled: this.volumeEncryptionFeatureEnabled && !this.isEncrypted && canCreateImage,
|
||||||
icon: 'icon icon-lock',
|
icon: 'icon icon-lock',
|
||||||
label: this.t('harvester.action.encryptImage'),
|
label: this.t('harvester.action.encryptImage'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'decryptImage',
|
action: 'decryptImage',
|
||||||
enabled: this.volumeEncryptionFeatureEnabled && this.isEncrypted,
|
enabled: this.volumeEncryptionFeatureEnabled && this.isEncrypted && canCreateImage,
|
||||||
icon: 'icon icon-unlock',
|
icon: 'icon icon-unlock',
|
||||||
label: this.t('harvester.action.decryptImage'),
|
label: this.t('harvester.action.decryptImage'),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -130,6 +130,8 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
clone.action = 'goToCloneVM';
|
clone.action = 'goToCloneVM';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canCreateVMSSchedule = !!this.$getters?.['schemaFor']?.(HCI.SCHEDULE_VM_BACKUP)?.collectionMethods?.find((x) => ['post'].includes(x.toLowerCase()));
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
action: 'stopVM',
|
action: 'stopVM',
|
||||||
@ -207,7 +209,7 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'createSchedule',
|
action: 'createSchedule',
|
||||||
enabled: this.schedulingVMBackupFeatureEnabled,
|
enabled: canCreateVMSSchedule && this.schedulingVMBackupFeatureEnabled,
|
||||||
icon: 'icon icon-history',
|
icon: 'icon icon-history',
|
||||||
label: this.t('harvester.action.createSchedule')
|
label: this.t('harvester.action.createSchedule')
|
||||||
},
|
},
|
||||||
|
|||||||
19
pkg/harvester/models/management.cattle.io.project.js
Normal file
19
pkg/harvester/models/management.cattle.io.project.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
import shellProject from '@shell/models/management.cattle.io.project';
|
||||||
|
|
||||||
|
// This model controls `Project / Namespace` page in rancher integration mode
|
||||||
|
// Extend management.cattle.io.project model from shell
|
||||||
|
export default class Project extends shellProject {
|
||||||
|
get _availableActions() {
|
||||||
|
const canUpdate = !!this.linkFor('update');
|
||||||
|
|
||||||
|
// disable `Edit Config` action if user does not have update permission.
|
||||||
|
return super._availableActions.map((action) => {
|
||||||
|
if (action.action === 'goToEdit') {
|
||||||
|
return { ...action, enabled: canUpdate };
|
||||||
|
}
|
||||||
|
|
||||||
|
return action;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -57,7 +57,11 @@ export default class HciVlanConfig extends HarvesterResource {
|
|||||||
get _availableActions() {
|
get _availableActions() {
|
||||||
const out = super._availableActions;
|
const out = super._availableActions;
|
||||||
|
|
||||||
insertAt(out, 0, this.migrateAction);
|
const canMigrate = !!this.$getters?.['schemaFor']?.(HCI.VLAN_CONFIG)?.collectionMethods?.find((x) => ['post'].includes(x.toLowerCase()));
|
||||||
|
|
||||||
|
if (canMigrate) {
|
||||||
|
insertAt(out, 0, this.migrateAction);
|
||||||
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -123,5 +123,26 @@ export default {
|
|||||||
const clusterId = currentCluster.id;
|
const clusterId = currentCluster.id;
|
||||||
|
|
||||||
return projectsInAllClusters.filter((project: any) => project.spec.clusterName === clusterId && project.nameDisplay !== 'System');
|
return projectsInAllClusters.filter((project: any) => project.spec.clusterName === clusterId && project.nameDisplay !== 'System');
|
||||||
}
|
},
|
||||||
|
|
||||||
|
// Few harvester resources name and REAL resource are different. E.g. HCI.NETWORK_ATTACHMENT page resource is NETWORK_ATTACHMENT.
|
||||||
|
// Check in config/harvester-cluster.js for more details.
|
||||||
|
// We need to look up the schema by resource name, and fallback to find using real resource name
|
||||||
|
schemaFor: (state, getters, rootState, rootGetters) => (type, _fuzzy = false, _allowThrow = true) => {
|
||||||
|
// follow the same logic as type-map/schemaFor in /dashboard/shell/plugins/dashboard-store/getters.js
|
||||||
|
const normalizedType = getters.normalizeType(type);
|
||||||
|
const schemas = state.types['schema'];
|
||||||
|
const out = schemas?.map?.get(normalizedType);
|
||||||
|
|
||||||
|
if (out) return out;
|
||||||
|
|
||||||
|
// if not found, use the resource mapping in configureType for a second try
|
||||||
|
const resourceType = rootGetters['type-map/optionsFor'](type)?.resource;
|
||||||
|
if (resourceType && resourceType !== type) {
|
||||||
|
const normalizedResource = getters.normalizeType(resourceType);
|
||||||
|
return schemas?.map?.get(normalizedResource) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user