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;
|
||||
},
|
||||
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,
|
||||
resourceDetail: HCI.NETWORK_ATTACHMENT,
|
||||
resourceEdit: HCI.NETWORK_ATTACHMENT
|
||||
resourceEdit: HCI.NETWORK_ATTACHMENT,
|
||||
});
|
||||
|
||||
virtualType({
|
||||
|
||||
@ -3,7 +3,6 @@ import { Banner } from '@components/Banner';
|
||||
import Loading from '@shell/components/Loading';
|
||||
import ResourceTable from '@shell/components/ResourceTable';
|
||||
import BadgeState from '@shell/components/formatter/BadgeStateFormatter';
|
||||
|
||||
import { NAME, AGE, NAMESPACE, STATE } from '@shell/config/table-headers';
|
||||
import { NETWORK_ATTACHMENT, SCHEMA } from '@shell/config/types';
|
||||
import { allHash } from '@shell/utils/promise';
|
||||
|
||||
@ -30,11 +30,12 @@ const STATUS_DISPLAY = {
|
||||
export default class PCIDevice extends SteveModel {
|
||||
get _availableActions() {
|
||||
const out = super._availableActions;
|
||||
const canUpdate = !!this.linkFor('update');
|
||||
|
||||
out.push(
|
||||
{
|
||||
action: 'enablePassthroughBulk',
|
||||
enabled: !this.isEnabling && !this.isvGPUDevice,
|
||||
enabled: !this.isEnabling && !this.isvGPUDevice && canUpdate,
|
||||
icon: 'icon icon-fw icon-dot',
|
||||
label: 'Enable Passthrough',
|
||||
bulkable: true,
|
||||
@ -43,7 +44,7 @@ export default class PCIDevice extends SteveModel {
|
||||
},
|
||||
{
|
||||
action: 'disablePassthrough',
|
||||
enabled: this.isEnabling && this.claimedByMe && !this.isvGPUDevice,
|
||||
enabled: this.isEnabling && this.claimedByMe && !this.isvGPUDevice && canUpdate,
|
||||
icon: 'icon icon-fw icon-dot-open',
|
||||
label: 'Disable Passthrough',
|
||||
bulkable: true,
|
||||
|
||||
@ -24,7 +24,7 @@ const OBSCURE_NAMESPACE_PREFIX = [
|
||||
|
||||
export default class HciNamespace extends namespace {
|
||||
get _availableActions() {
|
||||
const out = super._availableActions;
|
||||
let out = super._availableActions;
|
||||
const remove = out.findIndex((a) => a.action === 'promptRemove');
|
||||
|
||||
const promptRemove = {
|
||||
@ -53,6 +53,16 @@ export default class HciNamespace extends namespace {
|
||||
insertAt(out, out.length - 1, promptRemove);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,20 @@ import Secret from '@shell/models/secret';
|
||||
import { NAMESPACE } from '@shell/config/types';
|
||||
|
||||
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.
|
||||
// See isFullPageOverride in https://github.com/rancher/dashboard/blob/master/shell/components/ResourceDetail/index.vue
|
||||
get fullDetailPageOverride() {
|
||||
|
||||
@ -106,6 +106,15 @@ export default class HciStorageClass extends StorageClass {
|
||||
|
||||
get 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()) {
|
||||
out = out.filter((action) => {
|
||||
|
||||
@ -21,9 +21,10 @@ export default class HciAddonConfig extends HarvesterResource {
|
||||
out.push(rancherDashboard);
|
||||
}
|
||||
|
||||
const canUpdate = !!this.linkFor('update');
|
||||
const toggleAddon = {
|
||||
action: 'toggleAddon',
|
||||
enabled: true,
|
||||
enabled: canUpdate,
|
||||
icon: this.spec.enabled ? 'icon icon-pause' : 'icon icon-play',
|
||||
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';
|
||||
|
||||
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() {
|
||||
const conditions = get(this, 'status.conditions');
|
||||
const status = (findBy(conditions, 'type', 'validated') || {}).status ;
|
||||
|
||||
@ -19,16 +19,18 @@ export default class ScheduleVmBackup extends HarvesterResource {
|
||||
}
|
||||
});
|
||||
|
||||
const canUpdate = !!this.linkFor('update');
|
||||
|
||||
return [
|
||||
{
|
||||
action: 'resumeSchedule',
|
||||
enabled: ucFirst(this.state) === STATES.suspended.label,
|
||||
enabled: canUpdate && ucFirst(this.state) === STATES.suspended.label,
|
||||
icon: 'icons icon-play',
|
||||
label: this.t('harvester.action.resumeSchedule'),
|
||||
},
|
||||
{
|
||||
action: 'suspendSchedule',
|
||||
enabled: ucFirst(this.state) === STATES.active.label,
|
||||
enabled: canUpdate && ucFirst(this.state) === STATES.active.label,
|
||||
icon: 'icons icon-pause',
|
||||
label: this.t('harvester.action.suspendSchedule'),
|
||||
},
|
||||
|
||||
@ -52,6 +52,7 @@ export default class HciVmImage extends HarvesterResource {
|
||||
canCreateVM = false;
|
||||
}
|
||||
|
||||
const canCreateImage = !!this.$getters?.['schemaFor']?.(HCI.IMAGE)?.collectionMethods?.some((method) => method.toLowerCase() === 'post');
|
||||
const customActions = this.isReady ? [
|
||||
{
|
||||
action: 'createFromImage',
|
||||
@ -61,13 +62,13 @@ export default class HciVmImage extends HarvesterResource {
|
||||
},
|
||||
{
|
||||
action: 'encryptImage',
|
||||
enabled: this.volumeEncryptionFeatureEnabled && !this.isEncrypted,
|
||||
enabled: this.volumeEncryptionFeatureEnabled && !this.isEncrypted && canCreateImage,
|
||||
icon: 'icon icon-lock',
|
||||
label: this.t('harvester.action.encryptImage'),
|
||||
},
|
||||
{
|
||||
action: 'decryptImage',
|
||||
enabled: this.volumeEncryptionFeatureEnabled && this.isEncrypted,
|
||||
enabled: this.volumeEncryptionFeatureEnabled && this.isEncrypted && canCreateImage,
|
||||
icon: 'icon icon-unlock',
|
||||
label: this.t('harvester.action.decryptImage'),
|
||||
},
|
||||
|
||||
@ -130,6 +130,8 @@ export default class VirtVm extends HarvesterResource {
|
||||
clone.action = 'goToCloneVM';
|
||||
}
|
||||
|
||||
const canCreateVMSSchedule = !!this.$getters?.['schemaFor']?.(HCI.SCHEDULE_VM_BACKUP)?.collectionMethods?.find((x) => ['post'].includes(x.toLowerCase()));
|
||||
|
||||
return [
|
||||
{
|
||||
action: 'stopVM',
|
||||
@ -207,7 +209,7 @@ export default class VirtVm extends HarvesterResource {
|
||||
},
|
||||
{
|
||||
action: 'createSchedule',
|
||||
enabled: this.schedulingVMBackupFeatureEnabled,
|
||||
enabled: canCreateVMSSchedule && this.schedulingVMBackupFeatureEnabled,
|
||||
icon: 'icon icon-history',
|
||||
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() {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -123,5 +123,26 @@ export default {
|
||||
const clusterId = currentCluster.id;
|
||||
|
||||
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