mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-06-13 13:32:20 +00:00
fix: vGPU and USB enable/disable actions needs to be hidden for read only users (#877)
* fix: vGPU / USB enable/disable actions needs to be hidden for read only users Signed-off-by: Andy Lee <andy.lee@suse.com> * style: add scoped styles for group actions in DeviceList component Signed-off-by: Andy Lee <andy.lee@suse.com> * style: remove width property from group actions in DeviceList components Signed-off-by: Andy Lee <andy.lee@suse.com> * fix: update logging type in init function and improve SideNav visibility handling Signed-off-by: Andy Lee <andy.lee@suse.com> * fix: ensure canManageGroup only returns true for non-empty rows with updatable permissions Signed-off-by: Andy Lee <andy.lee@suse.com> --------- Signed-off-by: Andy Lee <andy.lee@suse.com>
This commit is contained in:
parent
09e8946cc3
commit
91232beffc
@ -237,6 +237,7 @@ export function init($plugin, store) {
|
|||||||
labelKey: 'harvester.addons.vmImport.labels.vmimport',
|
labelKey: 'harvester.addons.vmImport.labels.vmimport',
|
||||||
group: 'vmimport',
|
group: 'vmimport',
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
|
ifHaveType: HCI.VMIMPORT,
|
||||||
route: {
|
route: {
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
params: { resource: HCI.VMIMPORT }
|
params: { resource: HCI.VMIMPORT }
|
||||||
@ -266,6 +267,7 @@ export function init($plugin, store) {
|
|||||||
labelKey: 'harvester.addons.vmImport.labels.vmimportSourceVMWare',
|
labelKey: 'harvester.addons.vmImport.labels.vmimportSourceVMWare',
|
||||||
group: 'vmimport',
|
group: 'vmimport',
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
|
ifHaveType: HCI.VMIMPORT_SOURCE_V,
|
||||||
route: {
|
route: {
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
params: { resource: HCI.VMIMPORT_SOURCE_V }
|
params: { resource: HCI.VMIMPORT_SOURCE_V }
|
||||||
@ -295,6 +297,7 @@ export function init($plugin, store) {
|
|||||||
labelKey: 'harvester.addons.vmImport.labels.vmimportSourceOpenStack',
|
labelKey: 'harvester.addons.vmImport.labels.vmimportSourceOpenStack',
|
||||||
group: 'vmimport',
|
group: 'vmimport',
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
|
ifHaveType: HCI.VMIMPORT_SOURCE_O,
|
||||||
route: {
|
route: {
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
params: { resource: HCI.VMIMPORT_SOURCE_O }
|
params: { resource: HCI.VMIMPORT_SOURCE_O }
|
||||||
@ -323,6 +326,7 @@ export function init($plugin, store) {
|
|||||||
labelKey: 'harvester.addons.vmImport.labels.vmimportSourceOVA',
|
labelKey: 'harvester.addons.vmImport.labels.vmimportSourceOVA',
|
||||||
group: 'vmimport',
|
group: 'vmimport',
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
|
ifHaveType: HCI.VMIMPORT_SOURCE_OVA,
|
||||||
route: {
|
route: {
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
params: { resource: HCI.VMIMPORT_SOURCE_OVA }
|
params: { resource: HCI.VMIMPORT_SOURCE_OVA }
|
||||||
@ -484,6 +488,7 @@ export function init($plugin, store) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
virtualType({
|
virtualType({
|
||||||
|
ifHaveType: LOGGING.CLUSTER_FLOW,
|
||||||
labelKey: 'harvester.logging.clusterFlow.label',
|
labelKey: 'harvester.logging.clusterFlow.label',
|
||||||
name: HCI.CLUSTER_FLOW,
|
name: HCI.CLUSTER_FLOW,
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
@ -507,6 +512,7 @@ export function init($plugin, store) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
virtualType({
|
virtualType({
|
||||||
|
ifHaveType: LOGGING.CLUSTER_OUTPUT,
|
||||||
labelKey: 'harvester.logging.clusterOutput.label',
|
labelKey: 'harvester.logging.clusterOutput.label',
|
||||||
name: HCI.CLUSTER_OUTPUT,
|
name: HCI.CLUSTER_OUTPUT,
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
@ -530,6 +536,7 @@ export function init($plugin, store) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
virtualType({
|
virtualType({
|
||||||
|
ifHaveType: LOGGING.FLOW,
|
||||||
labelKey: 'harvester.logging.flow.label',
|
labelKey: 'harvester.logging.flow.label',
|
||||||
name: HCI.FLOW,
|
name: HCI.FLOW,
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
@ -553,6 +560,7 @@ export function init($plugin, store) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
virtualType({
|
virtualType({
|
||||||
|
ifHaveType: LOGGING.OUTPUT,
|
||||||
labelKey: 'harvester.logging.output.label',
|
labelKey: 'harvester.logging.output.label',
|
||||||
name: HCI.OUTPUT,
|
name: HCI.OUTPUT,
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
|
|||||||
@ -152,6 +152,10 @@ export default {
|
|||||||
return !rows.find((device) => !device.passthroughClaim);
|
return !rows.find((device) => !device.passthroughClaim);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
canManageGroup(rows = []) {
|
||||||
|
return rows.length > 0 && rows.every((row) => row.canUpdate === true);
|
||||||
|
},
|
||||||
|
|
||||||
changeRows(filterRows, parentSriov) {
|
changeRows(filterRows, parentSriov) {
|
||||||
this['filterRows'] = filterRows;
|
this['filterRows'] = filterRows;
|
||||||
this['parentSriov'] = parentSriov;
|
this['parentSriov'] = parentSriov;
|
||||||
@ -184,6 +188,10 @@ export default {
|
|||||||
:ref="group.key"
|
:ref="group.key"
|
||||||
v-trim-whitespace
|
v-trim-whitespace
|
||||||
class="group-tab"
|
class="group-tab"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="canManageGroup(group.rows)"
|
||||||
|
class="group-actions"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="groupIsAllEnabled(group.rows)"
|
v-if="groupIsAllEnabled(group.rows)"
|
||||||
@ -201,6 +209,7 @@ export default {
|
|||||||
>
|
>
|
||||||
{{ t('harvester.pci.enableGroup') }}
|
{{ t('harvester.pci.enableGroup') }}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
<span v-clean-html="group.key" />
|
<span v-clean-html="group.key" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -232,3 +241,9 @@ export default {
|
|||||||
</template>
|
</template>
|
||||||
</ResourceTable>
|
</ResourceTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.group-actions {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -113,6 +113,11 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
canManageGroup(rows = []) {
|
||||||
|
return rows.every((row) => row.canUpdate === true);
|
||||||
|
},
|
||||||
|
|
||||||
groupIsAllEnabled(rows = []) {
|
groupIsAllEnabled(rows = []) {
|
||||||
return !rows.find((device) => !device.passthroughClaim);
|
return !rows.find((device) => !device.passthroughClaim);
|
||||||
},
|
},
|
||||||
@ -152,6 +157,10 @@ export default {
|
|||||||
:ref="group.key"
|
:ref="group.key"
|
||||||
v-trim-whitespace
|
v-trim-whitespace
|
||||||
class="group-tab"
|
class="group-tab"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="canManageGroup(group.rows)"
|
||||||
|
class="group-actions"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="groupIsAllEnabled(group.rows)"
|
v-if="groupIsAllEnabled(group.rows)"
|
||||||
@ -169,6 +178,7 @@ export default {
|
|||||||
>
|
>
|
||||||
{{ t('harvester.usb.enableGroup') }}
|
{{ t('harvester.usb.enableGroup') }}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
<span v-clean-html="group.key" />
|
<span v-clean-html="group.key" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -181,3 +191,9 @@ export default {
|
|||||||
</template>
|
</template>
|
||||||
</ResourceTable>
|
</ResourceTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.group-actions {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import HarvesterResource from './harvester';
|
|||||||
export default class MIGCONFIGURATION extends HarvesterResource {
|
export default class MIGCONFIGURATION extends HarvesterResource {
|
||||||
get _availableActions() {
|
get _availableActions() {
|
||||||
let out = super._availableActions;
|
let out = super._availableActions;
|
||||||
|
const canUpdate = !!this.linkFor('update');
|
||||||
|
|
||||||
out = out.map((action) => {
|
out = out.map((action) => {
|
||||||
if (action.action === 'showConfiguration') {
|
if (action.action === 'showConfiguration') {
|
||||||
@ -26,13 +27,13 @@ export default class MIGCONFIGURATION extends HarvesterResource {
|
|||||||
out.push(
|
out.push(
|
||||||
{
|
{
|
||||||
action: 'enableConfig',
|
action: 'enableConfig',
|
||||||
enabled: !this.isEnabled,
|
enabled: !this.isEnabled && canUpdate,
|
||||||
icon: 'icon icon-fw icon-dot',
|
icon: 'icon icon-fw icon-dot',
|
||||||
label: 'Enable',
|
label: 'Enable',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'disableConfig',
|
action: 'disableConfig',
|
||||||
enabled: this.isEnabled,
|
enabled: this.isEnabled && canUpdate,
|
||||||
icon: 'icon icon-fw icon-dot-open',
|
icon: 'icon icon-fw icon-dot-open',
|
||||||
label: 'Disable',
|
label: 'Disable',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -30,12 +30,11 @@ 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 && canUpdate,
|
enabled: !this.isEnabling && !this.isvGPUDevice && this.canUpdate,
|
||||||
icon: 'icon icon-fw icon-dot',
|
icon: 'icon icon-fw icon-dot',
|
||||||
label: 'Enable Passthrough',
|
label: 'Enable Passthrough',
|
||||||
bulkable: true,
|
bulkable: true,
|
||||||
@ -44,7 +43,7 @@ export default class PCIDevice extends SteveModel {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'disablePassthrough',
|
action: 'disablePassthrough',
|
||||||
enabled: this.isEnabling && this.claimedByMe && !this.isvGPUDevice && canUpdate,
|
enabled: this.isEnabling && this.claimedByMe && !this.isvGPUDevice && this.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,
|
||||||
@ -55,6 +54,10 @@ export default class PCIDevice extends SteveModel {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get canUpdate() {
|
||||||
|
return !!this.linkFor('update');
|
||||||
|
}
|
||||||
|
|
||||||
get isvGPUDevice() {
|
get isvGPUDevice() {
|
||||||
if (!this.vGPUAsPCIDeviceFeatureEnabled) {
|
if (!this.vGPUAsPCIDeviceFeatureEnabled) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -16,13 +16,13 @@ export default class SRIOVDevice extends SteveModel {
|
|||||||
out.push(
|
out.push(
|
||||||
{
|
{
|
||||||
action: 'enableDevice',
|
action: 'enableDevice',
|
||||||
enabled: !this.isEnabled,
|
enabled: !this.isEnabled && this.canUpdate,
|
||||||
icon: 'icon icon-fw icon-dot',
|
icon: 'icon icon-fw icon-dot',
|
||||||
label: 'Enable',
|
label: 'Enable',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'disableDevice',
|
action: 'disableDevice',
|
||||||
enabled: this.isEnabled,
|
enabled: this.isEnabled && this.canUpdate,
|
||||||
icon: 'icon icon-fw icon-dot-open',
|
icon: 'icon icon-fw icon-dot-open',
|
||||||
label: 'Disable',
|
label: 'Disable',
|
||||||
},
|
},
|
||||||
@ -31,6 +31,10 @@ export default class SRIOVDevice extends SteveModel {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get canUpdate() {
|
||||||
|
return !!this.linkFor('update');
|
||||||
|
}
|
||||||
|
|
||||||
get canYaml() {
|
get canYaml() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export default class USBDevice extends SteveModel {
|
|||||||
out.push(
|
out.push(
|
||||||
{
|
{
|
||||||
action: 'enablePassthroughBulk',
|
action: 'enablePassthroughBulk',
|
||||||
enabled: !this.passthroughClaim && !this.status.enabled,
|
enabled: !this.passthroughClaim && !this.status.enabled && this.canUpdate,
|
||||||
icon: 'icon icon-fw icon-dot',
|
icon: 'icon icon-fw icon-dot',
|
||||||
label: 'Enable Passthrough',
|
label: 'Enable Passthrough',
|
||||||
bulkable: true,
|
bulkable: true,
|
||||||
@ -42,7 +42,7 @@ export default class USBDevice extends SteveModel {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'disablePassthrough',
|
action: 'disablePassthrough',
|
||||||
enabled: this.status.enabled,
|
enabled: this.status.enabled && this.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,
|
||||||
@ -53,6 +53,10 @@ export default class USBDevice extends SteveModel {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get canUpdate() {
|
||||||
|
return !!this.linkFor('update');
|
||||||
|
}
|
||||||
|
|
||||||
get canYaml() {
|
get canYaml() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,17 +27,18 @@ const STATUS_DISPLAY = {
|
|||||||
export default class VGpuDevice extends SteveModel {
|
export default class VGpuDevice extends SteveModel {
|
||||||
get _availableActions() {
|
get _availableActions() {
|
||||||
const out = super._availableActions;
|
const out = super._availableActions;
|
||||||
|
const canUpdate = !!this.linkFor('update');
|
||||||
|
|
||||||
out.push(
|
out.push(
|
||||||
{
|
{
|
||||||
action: 'enableVGpu',
|
action: 'enableVGpu',
|
||||||
enabled: !this.isEnabled,
|
enabled: !this.isEnabled && canUpdate,
|
||||||
icon: 'icon icon-fw icon-dot',
|
icon: 'icon icon-fw icon-dot',
|
||||||
label: 'Enable',
|
label: 'Enable',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'disableVGpu',
|
action: 'disableVGpu',
|
||||||
enabled: this.isEnabled,
|
enabled: this.isEnabled && canUpdate,
|
||||||
icon: 'icon icon-fw icon-dot-open',
|
icon: 'icon icon-fw icon-dot-open',
|
||||||
label: 'Disable',
|
label: 'Disable',
|
||||||
bulkable: true,
|
bulkable: true,
|
||||||
|
|||||||
@ -23,17 +23,17 @@ export default class HciVmTemplateVersion extends HarvesterResource {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const schema = this.$getters['schemaFor'](HCI.VM);
|
const schema = this.$getters['schemaFor'](HCI.VM);
|
||||||
let canCreateVM = true;
|
let canCreateVM = false;
|
||||||
|
|
||||||
if ( schema && !schema?.collectionMethods.find((x) => ['post'].includes(x.toLowerCase())) ) {
|
if (schema?.collectionMethods.find((x) => ['post'].includes(x.toLowerCase())) ) {
|
||||||
canCreateVM = false;
|
canCreateVM = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
action: 'launchFromTemplate',
|
action: 'launchFromTemplate',
|
||||||
icon: 'icon icon-spinner',
|
icon: 'icon icon-spinner',
|
||||||
disabled: !canCreateVM || !this.isReady,
|
enabled: canCreateVM && this.isReady,
|
||||||
label: this.t('harvester.action.launchFormTemplate'),
|
label: this.t('harvester.action.launchFormTemplate'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -31,22 +31,42 @@ export function registerAddonSideNav(store, productName, {
|
|||||||
}, 600);
|
}, 600);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Adds or removes the resource IDs from the product visibility whitelist.
|
const hasAccessibleSchema = (t) => {
|
||||||
const setMenuVisibility = (visible) => {
|
try {
|
||||||
if (visible) {
|
return !!store.getters[`${ productName }/schemaFor`]?.(t);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const showTypes = (visibleTypes) => {
|
||||||
store.commit('type-map/basicType', {
|
store.commit('type-map/basicType', {
|
||||||
product: productName,
|
product: productName,
|
||||||
group: navGroup,
|
group: navGroup,
|
||||||
types
|
types: visibleTypes
|
||||||
});
|
});
|
||||||
} else {
|
};
|
||||||
// Manually delete the keys from the state object to hide them.
|
|
||||||
|
const hideTypes = () => {
|
||||||
const basicTypes = store.state['type-map'].basicTypes[productName];
|
const basicTypes = store.state['type-map'].basicTypes[productName];
|
||||||
|
|
||||||
if (basicTypes) {
|
if (basicTypes) {
|
||||||
types.forEach((t) => delete basicTypes[t]);
|
types.forEach((t) => delete basicTypes[t]);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Adds or removes the resource IDs from the product visibility whitelist.
|
||||||
|
const setMenuVisibility = (visible) => {
|
||||||
|
const accessibleTypes = visible ? types.filter(hasAccessibleSchema) : [];
|
||||||
|
|
||||||
|
// Always clear first to remove any previously-registered types that are
|
||||||
|
// no longer accessible (e.g. partial permission changes like types=[A,B] where B is dropped).
|
||||||
|
hideTypes();
|
||||||
|
|
||||||
|
if (accessibleTypes.length > 0) {
|
||||||
|
showTypes(accessibleTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
kickSideNav();
|
kickSideNav();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user