mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-06-13 05:22:21 +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',
|
||||
group: 'vmimport',
|
||||
namespaced: true,
|
||||
ifHaveType: HCI.VMIMPORT,
|
||||
route: {
|
||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||
params: { resource: HCI.VMIMPORT }
|
||||
@ -266,6 +267,7 @@ export function init($plugin, store) {
|
||||
labelKey: 'harvester.addons.vmImport.labels.vmimportSourceVMWare',
|
||||
group: 'vmimport',
|
||||
namespaced: true,
|
||||
ifHaveType: HCI.VMIMPORT_SOURCE_V,
|
||||
route: {
|
||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||
params: { resource: HCI.VMIMPORT_SOURCE_V }
|
||||
@ -295,6 +297,7 @@ export function init($plugin, store) {
|
||||
labelKey: 'harvester.addons.vmImport.labels.vmimportSourceOpenStack',
|
||||
group: 'vmimport',
|
||||
namespaced: true,
|
||||
ifHaveType: HCI.VMIMPORT_SOURCE_O,
|
||||
route: {
|
||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||
params: { resource: HCI.VMIMPORT_SOURCE_O }
|
||||
@ -323,6 +326,7 @@ export function init($plugin, store) {
|
||||
labelKey: 'harvester.addons.vmImport.labels.vmimportSourceOVA',
|
||||
group: 'vmimport',
|
||||
namespaced: true,
|
||||
ifHaveType: HCI.VMIMPORT_SOURCE_OVA,
|
||||
route: {
|
||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||
params: { resource: HCI.VMIMPORT_SOURCE_OVA }
|
||||
@ -484,6 +488,7 @@ export function init($plugin, store) {
|
||||
});
|
||||
|
||||
virtualType({
|
||||
ifHaveType: LOGGING.CLUSTER_FLOW,
|
||||
labelKey: 'harvester.logging.clusterFlow.label',
|
||||
name: HCI.CLUSTER_FLOW,
|
||||
namespaced: true,
|
||||
@ -507,6 +512,7 @@ export function init($plugin, store) {
|
||||
});
|
||||
|
||||
virtualType({
|
||||
ifHaveType: LOGGING.CLUSTER_OUTPUT,
|
||||
labelKey: 'harvester.logging.clusterOutput.label',
|
||||
name: HCI.CLUSTER_OUTPUT,
|
||||
namespaced: true,
|
||||
@ -530,6 +536,7 @@ export function init($plugin, store) {
|
||||
});
|
||||
|
||||
virtualType({
|
||||
ifHaveType: LOGGING.FLOW,
|
||||
labelKey: 'harvester.logging.flow.label',
|
||||
name: HCI.FLOW,
|
||||
namespaced: true,
|
||||
@ -553,6 +560,7 @@ export function init($plugin, store) {
|
||||
});
|
||||
|
||||
virtualType({
|
||||
ifHaveType: LOGGING.OUTPUT,
|
||||
labelKey: 'harvester.logging.output.label',
|
||||
name: HCI.OUTPUT,
|
||||
namespaced: true,
|
||||
|
||||
@ -152,6 +152,10 @@ export default {
|
||||
return !rows.find((device) => !device.passthroughClaim);
|
||||
},
|
||||
|
||||
canManageGroup(rows = []) {
|
||||
return rows.length > 0 && rows.every((row) => row.canUpdate === true);
|
||||
},
|
||||
|
||||
changeRows(filterRows, parentSriov) {
|
||||
this['filterRows'] = filterRows;
|
||||
this['parentSriov'] = parentSriov;
|
||||
@ -185,22 +189,27 @@ export default {
|
||||
v-trim-whitespace
|
||||
class="group-tab"
|
||||
>
|
||||
<button
|
||||
v-if="groupIsAllEnabled(group.rows)"
|
||||
type="button"
|
||||
class="btn btn-sm role-secondary mr-5"
|
||||
@click="e=>{disableGroup(group.rows); e.target.blur()}"
|
||||
<div
|
||||
v-if="canManageGroup(group.rows)"
|
||||
class="group-actions"
|
||||
>
|
||||
{{ t('harvester.pci.disableGroup') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
type="button"
|
||||
class="btn btn-sm role-secondary mr-5"
|
||||
@click="e=>{enableGroup(group.rows); e.target.blur()}"
|
||||
>
|
||||
{{ t('harvester.pci.enableGroup') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="groupIsAllEnabled(group.rows)"
|
||||
type="button"
|
||||
class="btn btn-sm role-secondary mr-5"
|
||||
@click="e=>{disableGroup(group.rows); e.target.blur()}"
|
||||
>
|
||||
{{ t('harvester.pci.disableGroup') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
type="button"
|
||||
class="btn btn-sm role-secondary mr-5"
|
||||
@click="e=>{enableGroup(group.rows); e.target.blur()}"
|
||||
>
|
||||
{{ t('harvester.pci.enableGroup') }}
|
||||
</button>
|
||||
</div>
|
||||
<span v-clean-html="group.key" />
|
||||
</div>
|
||||
</template>
|
||||
@ -232,3 +241,9 @@ export default {
|
||||
</template>
|
||||
</ResourceTable>
|
||||
</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 = []) {
|
||||
return !rows.find((device) => !device.passthroughClaim);
|
||||
},
|
||||
@ -153,22 +158,27 @@ export default {
|
||||
v-trim-whitespace
|
||||
class="group-tab"
|
||||
>
|
||||
<button
|
||||
v-if="groupIsAllEnabled(group.rows)"
|
||||
type="button"
|
||||
class="btn btn-sm role-secondary mr-5"
|
||||
@click="e=>{disableGroup(group.rows); e.target.blur()}"
|
||||
<div
|
||||
v-if="canManageGroup(group.rows)"
|
||||
class="group-actions"
|
||||
>
|
||||
{{ t('harvester.usb.disableGroup') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
type="button"
|
||||
class="btn btn-sm role-secondary mr-5"
|
||||
@click="e=>{enableGroup(group.rows); e.target.blur()}"
|
||||
>
|
||||
{{ t('harvester.usb.enableGroup') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="groupIsAllEnabled(group.rows)"
|
||||
type="button"
|
||||
class="btn btn-sm role-secondary mr-5"
|
||||
@click="e=>{disableGroup(group.rows); e.target.blur()}"
|
||||
>
|
||||
{{ t('harvester.usb.disableGroup') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
type="button"
|
||||
class="btn btn-sm role-secondary mr-5"
|
||||
@click="e=>{enableGroup(group.rows); e.target.blur()}"
|
||||
>
|
||||
{{ t('harvester.usb.enableGroup') }}
|
||||
</button>
|
||||
</div>
|
||||
<span v-clean-html="group.key" />
|
||||
</div>
|
||||
</template>
|
||||
@ -181,3 +191,9 @@ export default {
|
||||
</template>
|
||||
</ResourceTable>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.group-actions {
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -9,6 +9,7 @@ import HarvesterResource from './harvester';
|
||||
export default class MIGCONFIGURATION extends HarvesterResource {
|
||||
get _availableActions() {
|
||||
let out = super._availableActions;
|
||||
const canUpdate = !!this.linkFor('update');
|
||||
|
||||
out = out.map((action) => {
|
||||
if (action.action === 'showConfiguration') {
|
||||
@ -26,13 +27,13 @@ export default class MIGCONFIGURATION extends HarvesterResource {
|
||||
out.push(
|
||||
{
|
||||
action: 'enableConfig',
|
||||
enabled: !this.isEnabled,
|
||||
enabled: !this.isEnabled && canUpdate,
|
||||
icon: 'icon icon-fw icon-dot',
|
||||
label: 'Enable',
|
||||
},
|
||||
{
|
||||
action: 'disableConfig',
|
||||
enabled: this.isEnabled,
|
||||
enabled: this.isEnabled && canUpdate,
|
||||
icon: 'icon icon-fw icon-dot-open',
|
||||
label: 'Disable',
|
||||
},
|
||||
|
||||
@ -30,12 +30,11 @@ 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 && canUpdate,
|
||||
enabled: !this.isEnabling && !this.isvGPUDevice && this.canUpdate,
|
||||
icon: 'icon icon-fw icon-dot',
|
||||
label: 'Enable Passthrough',
|
||||
bulkable: true,
|
||||
@ -44,7 +43,7 @@ export default class PCIDevice extends SteveModel {
|
||||
},
|
||||
{
|
||||
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',
|
||||
label: 'Disable Passthrough',
|
||||
bulkable: true,
|
||||
@ -55,6 +54,10 @@ export default class PCIDevice extends SteveModel {
|
||||
return out;
|
||||
}
|
||||
|
||||
get canUpdate() {
|
||||
return !!this.linkFor('update');
|
||||
}
|
||||
|
||||
get isvGPUDevice() {
|
||||
if (!this.vGPUAsPCIDeviceFeatureEnabled) {
|
||||
return false;
|
||||
|
||||
@ -16,13 +16,13 @@ export default class SRIOVDevice extends SteveModel {
|
||||
out.push(
|
||||
{
|
||||
action: 'enableDevice',
|
||||
enabled: !this.isEnabled,
|
||||
enabled: !this.isEnabled && this.canUpdate,
|
||||
icon: 'icon icon-fw icon-dot',
|
||||
label: 'Enable',
|
||||
},
|
||||
{
|
||||
action: 'disableDevice',
|
||||
enabled: this.isEnabled,
|
||||
enabled: this.isEnabled && this.canUpdate,
|
||||
icon: 'icon icon-fw icon-dot-open',
|
||||
label: 'Disable',
|
||||
},
|
||||
@ -31,6 +31,10 @@ export default class SRIOVDevice extends SteveModel {
|
||||
return out;
|
||||
}
|
||||
|
||||
get canUpdate() {
|
||||
return !!this.linkFor('update');
|
||||
}
|
||||
|
||||
get canYaml() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ export default class USBDevice extends SteveModel {
|
||||
out.push(
|
||||
{
|
||||
action: 'enablePassthroughBulk',
|
||||
enabled: !this.passthroughClaim && !this.status.enabled,
|
||||
enabled: !this.passthroughClaim && !this.status.enabled && this.canUpdate,
|
||||
icon: 'icon icon-fw icon-dot',
|
||||
label: 'Enable Passthrough',
|
||||
bulkable: true,
|
||||
@ -42,7 +42,7 @@ export default class USBDevice extends SteveModel {
|
||||
},
|
||||
{
|
||||
action: 'disablePassthrough',
|
||||
enabled: this.status.enabled,
|
||||
enabled: this.status.enabled && this.canUpdate,
|
||||
icon: 'icon icon-fw icon-dot-open',
|
||||
label: 'Disable Passthrough',
|
||||
bulkable: true,
|
||||
@ -53,6 +53,10 @@ export default class USBDevice extends SteveModel {
|
||||
return out;
|
||||
}
|
||||
|
||||
get canUpdate() {
|
||||
return !!this.linkFor('update');
|
||||
}
|
||||
|
||||
get canYaml() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -27,17 +27,18 @@ const STATUS_DISPLAY = {
|
||||
export default class VGpuDevice extends SteveModel {
|
||||
get _availableActions() {
|
||||
const out = super._availableActions;
|
||||
const canUpdate = !!this.linkFor('update');
|
||||
|
||||
out.push(
|
||||
{
|
||||
action: 'enableVGpu',
|
||||
enabled: !this.isEnabled,
|
||||
enabled: !this.isEnabled && canUpdate,
|
||||
icon: 'icon icon-fw icon-dot',
|
||||
label: 'Enable',
|
||||
},
|
||||
{
|
||||
action: 'disableVGpu',
|
||||
enabled: this.isEnabled,
|
||||
enabled: this.isEnabled && canUpdate,
|
||||
icon: 'icon icon-fw icon-dot-open',
|
||||
label: 'Disable',
|
||||
bulkable: true,
|
||||
|
||||
@ -23,17 +23,17 @@ export default class HciVmTemplateVersion extends HarvesterResource {
|
||||
});
|
||||
|
||||
const schema = this.$getters['schemaFor'](HCI.VM);
|
||||
let canCreateVM = true;
|
||||
let canCreateVM = false;
|
||||
|
||||
if ( schema && !schema?.collectionMethods.find((x) => ['post'].includes(x.toLowerCase())) ) {
|
||||
canCreateVM = false;
|
||||
if (schema?.collectionMethods.find((x) => ['post'].includes(x.toLowerCase())) ) {
|
||||
canCreateVM = true;
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
action: 'launchFromTemplate',
|
||||
icon: 'icon icon-spinner',
|
||||
disabled: !canCreateVM || !this.isReady,
|
||||
enabled: canCreateVM && this.isReady,
|
||||
label: this.t('harvester.action.launchFormTemplate'),
|
||||
},
|
||||
{
|
||||
|
||||
@ -31,22 +31,42 @@ export function registerAddonSideNav(store, productName, {
|
||||
}, 600);
|
||||
};
|
||||
|
||||
const hasAccessibleSchema = (t) => {
|
||||
try {
|
||||
return !!store.getters[`${ productName }/schemaFor`]?.(t);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const showTypes = (visibleTypes) => {
|
||||
store.commit('type-map/basicType', {
|
||||
product: productName,
|
||||
group: navGroup,
|
||||
types: visibleTypes
|
||||
});
|
||||
};
|
||||
|
||||
const hideTypes = () => {
|
||||
const basicTypes = store.state['type-map'].basicTypes[productName];
|
||||
|
||||
if (basicTypes) {
|
||||
types.forEach((t) => delete basicTypes[t]);
|
||||
}
|
||||
};
|
||||
|
||||
// Adds or removes the resource IDs from the product visibility whitelist.
|
||||
const setMenuVisibility = (visible) => {
|
||||
if (visible) {
|
||||
store.commit('type-map/basicType', {
|
||||
product: productName,
|
||||
group: navGroup,
|
||||
types
|
||||
});
|
||||
} else {
|
||||
// Manually delete the keys from the state object to hide them.
|
||||
const basicTypes = store.state['type-map'].basicTypes[productName];
|
||||
const accessibleTypes = visible ? types.filter(hasAccessibleSchema) : [];
|
||||
|
||||
if (basicTypes) {
|
||||
types.forEach((t) => delete basicTypes[t]);
|
||||
}
|
||||
// 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();
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user